Redis实现延迟队列

延迟消息队列的常见实现方式是通过 ZSet 的存储于查询来实现,它的核心思想是在程序中开启一个一直循环的延迟任务的检测器,用于检测和调用延迟任务的执行

如下图所示:

 

 方式一:zrangebyscore 查询所有任务

此实现方式是一次性查询出所有的延迟任务,然后再进行执行,实现代码如下:
import redis.clients.jedis.Jedis;
import utils.JedisUtils;

import java.time.Instant;
import java.util.Set;

/**
 * 延迟队列
 */
public class DelayQueueExample {
    // zset key
    private static final String _KEY = "myDelayQueue";

    public static void main(String[] args) throws InterruptedException {
        Jedis jedis = JedisUtils.getJedis();
        // 延迟 30s 执行(30s 后的时间)
        long delayTime = Instant.now().plusSeconds(30).getEpochSecond();
        jedis.zadd(_KEY, delayTime, "order_1");
        // 继续添加测试数据
        jedis.zadd(_KEY, Instant.now().plusSeconds(2).getEpochSecond(), "order_2");
        jedis.zadd(_KEY, Instant.now().plusSeconds(2).getEpochSecond(), "order_3");
        jedis.zadd(_KEY, Instant.now().plusSeconds(7).getEpochSecond(), "order_4");
        jedis.zadd(_KEY, Instant.now().plusSeconds(10).getEpochSecond(), "order_5");
        // 开启延迟队列
        doDelayQueue(jedis);
    }

    /**
     * 延迟队列消费
     * @param jedis Redis 客户端
     */
    public static void doDelayQueue(Jedis jedis) throws InterruptedException {
        while (true) {
            // 当前时间
            Instant nowInstant = Instant.now();
            long lastSecond = nowInstant.plusSeconds(-1).getEpochSecond(); // 上一秒时间
            long nowSecond = nowInstant.getEpochSecond();
            // 查询当前时间的所有任务
            Set<String> data = jedis.zrangeByScore(_KEY, lastSecond, nowSecond);
            for (String item : data) {
                // 消费任务
                System.out.println("消费:" + item);
            }
            // 删除已经执行的任务
            jedis.zremrangeByScore(_KEY, lastSecond, nowSecond);
            Thread.sleep(1000); // 每秒轮询一次
        }
    }
}
以上程序执行结果如下:

消费:order2 消费:order3 消费:order4 消费:order5 消费:order_1

方式二:判断最早的任务

此实现方式是每次查询最早的一条任务,再与当前时间进行判断,如果任务执行时间大于当前时间则表示应该立即执行延迟任务,实现代码如下:
import redis.clients.jedis.Jedis;
import utils.JedisUtils;

import java.time.Instant;
import java.util.Set;

/**
 * 延迟队列
 */
public class DelayQueueExample {
    // zset key
    private static final String _KEY = "myDelayQueue";

    public static void main(String[] args) throws InterruptedException {
        Jedis jedis = JedisUtils.getJedis();
        // 延迟 30s 执行(30s 后的时间)
        long delayTime = Instant.now().plusSeconds(30).getEpochSecond();
        jedis.zadd(_KEY, delayTime, "order_1");
        // 继续添加测试数据
        jedis.zadd(_KEY, Instant.now().plusSeconds(2).getEpochSecond(), "order_2");
        jedis.zadd(_KEY, Instant.now().plusSeconds(2).getEpochSecond(), "order_3");
        jedis.zadd(_KEY, Instant.now().plusSeconds(7).getEpochSecond(), "order_4");
        jedis.zadd(_KEY, Instant.now().plusSeconds(10).getEpochSecond(), "order_5");
        // 开启延迟队列
        doDelayQueue2(jedis);
    }

    /**
     * 延迟队列消费(方式 2)
     * @param jedis Redis 客户端
     */
    public static void doDelayQueue2(Jedis jedis) throws InterruptedException {
        while (true) {
            // 当前时间
            long nowSecond = Instant.now().getEpochSecond();
            // 每次查询一条消息,判断此消息的执行时间
            Set<String> data = jedis.zrange(_KEY, 0, 0);
            if (data.size() == 1) {
                String firstValue = data.iterator().next();
                // 消息执行时间
                Double score = jedis.zscore(_KEY, firstValue);
                if (nowSecond >= score) {
                    // 消费消息(业务功能处理)
                    System.out.println("消费消息:" + firstValue);
                    // 删除已经执行的任务
                    jedis.zrem(_KEY, firstValue);
                }
            }
            Thread.sleep(100); // 执行间隔
        }
    }
}
以上程序执行结果和实现方式一相同,结果如下:

消费:order2 消费:order3 消费:order4 消费:order5 消费:order_1

 

posted @ 2022-07-05 16:54  VNone  阅读(1946)  评论(0)    收藏  举报