redis是我们项目开发中常见的技术中间件,它除了可以实现常见的分布式锁和分布式缓存功能之外,还可以帮助我们实现很多的功能,如延迟队列。下面介绍几种redis常见的实现延迟队列的方案。
1、通过过期key通知实现

实现思路:首先开启redis的key过期通知,然后在业务中给key设置过期时间,到了过期时间后redis会自动的将过期的key消息推送给监听者,从而实现延迟任务。
核心的代码实现:
#1、开始redis的过期通知notify-keyspace-events Ex#2、监听redis的过期key@Component@Slf4jpublic class RedisExpireKeyService extendsKeyExpirationEventMessageListener {*** Creates new {@link MessageListener} for {@code __keyevent@*__:expired} messages.** @param listenerContainer must not be {@literal null}.*/public RedisExpireKeyService(RedisMessageListenerContainer listenerContainer) {super(listenerContainer);}*** 监听过期的key**/@Overridepublic void onMessage(Message message, byte[] pattern) {String expireKey = message.toString();执行具体的业务System.out.println("监听到key=" + expireKey + ",已经过期");}}
生产环境是不推荐使用此方案,原因Redis 的过期策略采用的是惰性删除和定期删除相结合的方式,redis并不保证 key 在过期时会被立即删除操作,此方案适用于平时自己项目练习的时候使用。
2、通过Zset数据类型+定时任务实现

实现思路:ZSet 是一种有序集合类型,它可以存储不重复的元素,并且给每个元素赋予一个 double 类型的排序权重值(score),所以可以将元素的过期时间作为分值,通过定时任务扫描的方式判断是否达到过期时间,从而实现延迟队列。
核心的代码实现:
#使用xxl-job@JobName("consumerTaskJob")public void consumerTaskJob() {String expireKey = "ExPIRE_KEY";try {//获取当前时间double currentTime = System.currentTimeMillis();//获取超时的数据Set<String> expiredMemberSet = redisTemplate.opsForZSet().rangeByScore(expireKey, Double.MIN_VALUE, currentTime);//过期keyfor (String expiredMember : expiredMemberSet) {//todo 做实际的延迟任务//从ZSet中移除数据redisTemplate.opsForZSet().remove(expireKey, expiredMember);}} catch (Exception e) {log.error("数据处理失败",e);}}
Zset+定时任务的实现延迟任务的方式虽然比监听过期key方案合理一些,但是它还是存在一定的缺陷,如无重试机制、延迟时间固定化(依赖定时任务的执行时间)、不适用于大规模的延迟任务。
3、Redisson实现延迟队列
Redisson是一个操作Redis的 Java 客户框架,它提供了RDelayedQueue 接口和 RQueue 接口可以实现延迟队列(Redisson 提供的延迟队列底层也是基于 Zset 数据结构实现的)。
核心的代码实现:
#1、添加依赖<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.16.0</version></dependency>#2、添加数据到队列中//创建RedissonClient实例RedissonClient redissonClient = Redisson.create();//创建阻塞队列RBlockingDeque<String> queue = redissonClient.getBlockingDeque("delayQueue");//创建延迟队列并关联到阻塞队列RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(queue);//添加延迟任务delayedQueue.offer("Task1", 5000, TimeUnit.MILLISECONDS);#3、消费数据while (true) {try {//获取并移除队首元素,如果队列为空,则阻塞等待String task = queue.take();System.out.println("Task: " + task);} catch (Exception e) {log.error("消费失败",e);}}
总结:Redis实现的延迟队列适用于处理一些比较简单的业务,如发送邮件、发送通知等,对于复杂的业务不适用于Redis的延迟任务方案。




