最大的网站模板网,网站建设项目甘特图,西宁seo网站建设,网站的动画效果代码Redis实现延时队列
延时队列里装的主要是延时任务#xff0c;用延时队列来维护延时任务的执行时间。
1、延时队列有哪些使用情景#xff1f;
1、如果请求加锁没加成功
可以将这个请求扔到延时队列里#xff0c;延后处理。
2、业务中有延时任务的需要
比如说#xff0…Redis实现延时队列
延时队列里装的主要是延时任务用延时队列来维护延时任务的执行时间。
1、延时队列有哪些使用情景
1、如果请求加锁没加成功
可以将这个请求扔到延时队列里延后处理。
2、业务中有延时任务的需要
比如说文章定时发布。
2、基于redis实现延时队列
2.1.使用zset实现 使用redis的zset来实现延时队列 使用zset的添加、查询、删除命令 ZADD命令 ZADD key score member [score member...] ZRANGEBYSCORE命令 ZRANGEBYSCORE key min max ZREM命令 ZREM key member [member ...] 1将延时任务的【到期执行时间作为zset的score】、【延时任务序列化为一个字符串作为zset的member】使用zadd命令装进zset中。 2zset会为这些延时任务按照到期执行时间排序。 3设置多线程轮询zset获取到期的任务 | 用Schedule注解标注定时轮询 4在zset中删除这些到期任务 5将这些到期任务放进list中在list中使用【blpop/brpop】消费任务。
优化1
对于第3步来说由于是多线程所以同一个到期任务可能会被多个进程获取到然后再使用 zrem 进行争抢。最终一定只有一个进程zrem成功。因此对于那些zrem没成功的进程相当于白取任务。所以我们可以进一步优化【使用 lua scripting 让 zrangebyscore 和 zrem 一同挪到服务器端进行原子化操作】。
优化2
使用redis管道操作通过管道方式将获取到的到期任务push到list中。
优化redis管道
1Redis的消息交互
客户端将请求传送给服务器服务器处理完毕后再将响应回复给客户端。这要花费一个网络数据包来回的时间。 详细过程是这样的 1、客户端进程调用 write 将消息写到操作系统内核为套接字分配的发送缓冲 send buffer。 2、客户端操作系统内核将发送缓冲的内容发送到网卡网卡硬件将数据通过「网际路由」送到服务器的网卡。 3、服务器操作系统内核将网卡的数据放到内核为套接字分配的接收缓冲 recv buffer。 4、服务器进程调用 read 从接收缓冲中取出消息进行处理。 5、服务器进程调用 write 将响应消息写到内核为套接字分配的发送缓冲 send buffer。 6、服务器操作系统内核将发送缓冲的内容发送到网卡网卡硬件将数据通过「网际路由」送到客户端的网卡。 7、客户端操作系统内核将网卡的数据放到内核为套接字分配的接收缓冲 recv buffer。 8、客户端进程调用 read 从接收缓冲中取出消息返回给上层业务逻辑进行处理。 9、结束。 由上面的详细过程可以知道
【write操作】只负责将数据写到发送缓冲。但是如果发送缓冲满了那么就需要等待缓冲空出空闲空间。这个就是写操作 IO 操作的真正耗时。
【read操作】只负责将数据从接收缓冲中取出来就完事了。如果缓冲是空的那么就需要等待数据到来这个就是读操作 IO 操作的真正耗时。
因此一条消息的消息交互所花费的时间主要在于
write 操作几乎没有耗时直接写到发送缓冲就返回而 read 就会比较耗时了因为它要等待消息经过网络路由到目标机器处理后的响应消息,再回送到当前的内核读缓冲才可以返回。这才是一个网络来回的真正开销。
2连续多条消息指令的消息交互
1普通方式
如果要执行多条消息指令则需要花费多个网络来回的时间。
2使用redis管道
管道方式是客户端通过将多条命令white到发送缓存中然后再一次性发送到服务端的缓存中服务端处理完这些命令后将响应结果写到发送缓存中最后再发送给接收缓存客户端第一次read的时候需要等待一个网络来回而后续的read操作直接从接收缓存中取就行了因此总的花费时间只有一个网络来回的时间。 以上两种方式的示例代码
public class RedisPipelineTestDemo {public static void main(String[] args) {//连接redisJedis jedis new Jedis(10.101.17.180, 6379);//jedis逐一给每个set新增一个valueString zSetKey Pipeline-test-set;int size 100000;//普通方式long begin System.currentTimeMillis();for (int i 0; i size; i) {jedis.sadd(zSetKey i, aaa);}log.info(Jedis逐一给每个set新增一个value耗时{}ms, (System.currentTimeMillis() - begin));//管道方式 Pipeline Pipeline jedis.Pipelined();begin System.currentTimeMillis();for (int i 0; i size; i) { Pipeline.sadd(zSetKey i, bbb);} Pipeline.sync();log.info(Jedis Pipeline模式耗时{}ms, (System.currentTimeMillis() - begin));}
}
普通方式162655ms
管道方式504ms
2.2.使用redis键空间通知
redis的PubSub发布者订阅者模型。 摘自javaguide 在 pub/sub 模式下生产者需要指定消息发送到哪个 channel 中而消费者则订阅对应的 channel 以获取消息。 Redis 中有很多默认的 channel这些 channel 是由 Redis 本身向它们发送消息的而不是我们自己编写的代码。其中__keyevent0__:expired 就是一个默认的 channel负责监听 key 的过期事件。也就是说当一个 key 过期之后Redis 会发布一个 key 过期的事件到__keyeventdb__:expired这个 channel 中。 我们只需要监听这个 channel就可以拿到过期的 key 的消息进而实现了延时任务功能。 这个功能Redis官方称为keyspace notifications字面意思就是键空间通知。 代码实现
Spring已经实现了监听__keyevent*__:expired这个channel这个功能__keyevent*__:expired中的*代表通配符的意思监听所有的数据库。
在配置类中
Configuration
public class RedisConfiguration {
Beanpublic RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory) {RedisMessageListenerContainer redisMessageListenerContainer new RedisMessageListenerContainer();redisMessageListenerContainer.setConnectionFactory(connectionFactory);return redisMessageListenerContainer;}
Beanpublic KeyExpirationEventMessageListener redisKeyExpirationListener(RedisMessageListenerContainer redisMessageListenerContainer) {return new KeyExpirationEventMessageListener(redisMessageListenerContainer);}/**KeyExpirationEventMessageListener实现了对__keyevent*__:expiredchannel的监听当KeyExpirationEventMessageListener收到Redis发布的过期Key的消息的时候会发布RedisKeyExpiredEvent事件**/
}所以我们只需要监听RedisKeyExpiredEvent事件就可以拿到过期消息的Key也就是延迟消息。对RedisKeyExpiredEvent事件的监听实现MyRedisKeyExpiredEventListener。
Component
public class MyRedisKeyExpiredEventListener implements ApplicationListenerRedisKeyExpiredEvent {
Overridepublic void onApplicationEvent(RedisKeyExpiredEvent event) {byte[] body event.getSource();System.out.println(获取到延迟消息 new String(body));}
}
缺点
1、能否及时的监听到过期键过期取决于【redis的过期键删除策略】。由于可能出现redis过期键已经过期但是redis还未将其删除从而导致无法监听到过期键过期。使得最后消息延迟。 补充redis过期键删除策略 定时删除 惰性删除
1定时删除
Redis 默认会每秒进行十次过期扫描过期扫描不会遍历过期字典中所有的 key而是采用了一种简单的贪心策略。
1、从过期字典中随机 20 个 key
2、删除这 20 个 key 中已经过期的 key
3、如果过期的 key 比率超过 1/4那就重复步骤 1
同时为了保证过期扫描不会出现循环过度导致线程卡死现象算法还增加了扫描时间的上限默认不会超过【 25ms】。
大量key同时过期存在的问题
但是如果一个Redis 实例中所有的 key 在同一时间过期即使有25ms的扫描时间上限如果此时有101 个客户端同时将请求发过来25ms后才能处理一个客户端的请求然后25ms后再处理一个客户端的请求那么第101个客户端需要等待2500ms后才能被处理请求。
大量key同时过期解决方法
给过期时间设置一个随机范围而不能全部在同一时间过期。
2惰性删除
在客户端访问这个 key 的时候redis 对 key 的过期时间进行检查如果过期了就立即删除。 参考
《Redis深度历险》
Redis常见面试题总结(上) | JavaGuide