Redis实现延迟队列

 

一、延迟队列

进入该队列的消息会被延迟消费的队列,一般的队列,进入队列后会进行排队依次消费掉

二、使用场景

需要进行延迟消费的场景,本文举例为某本书籍更新了章节,待内容上传完成及各种逻辑处理完成之后延迟五分钟给用户推送消息通知

三、使用介绍

实现方式还有很多,可以使用java延迟队列RelayQueue或者RabbitMQ来实现,这里记录redis实现

主要思路为Redis的ZSET集合来实现,score值为过期时间的时间戳,再创建线程池通过rangeByScore获取需要处理的数据

public interface IDelayQueue<E> {
    /**
     * 向延时队列中添加数据
     *
     * @param score 分数
     * @param data  数据
     * @return true 成功 false 失败
     */
    boolean add(long score, E data);

    /**
     * 从延时队列中获取数据
     *
     * @return
     */
    String get();

    /**
     * 删除数据
     *
     * @param data 数据
     * @return
     */
    boolean rem(E data);
}

  

public class RedisDelayQueue implements IDelayQueue<String> {

    private RedisTemplate<String, String> redisQueueTemplate;

    private String key;

    public RedisDelayQueue(String key, RedisTemplate<String, String> redisQueueTemplate) {
        this.key = key;
        this.redisQueueTemplate = redisQueueTemplate;
    }

    @Override
    public boolean add(long score, String data) {
        return redisQueueTemplate.opsForZSet().add(key, data, score);
    }

    @Override
    public String get() {
        double now = System.currentTimeMillis();
        double min = Double.MIN_VALUE;
        Set<String> res = redisQueueTemplate.opsForZSet().rangeByScore(key, min, now, 0, 10);
        if (!CollectionUtils.isEmpty(res)) {
            for (String data : res){
                // 删除成功,则进行处理,防止并发获取重复数据
                if (rem(data)){
                    return data;
                }
            }
        }
        return null;
    }


    @Override
    public boolean rem(String data) {
        return redisQueueTemplate.opsForZSet().remove(key, data) > 0;
    }

}

实例化RedisDealyQueu并且注入,  

private final IDelayQueue<String> queue;

 在需要往队列插入数据的地方:

 

       int delayMinutes = 5;
        LocalDateTime localDateTime = LocalDateTime.now().plusMinutes(delayMinutes);
        long score = localDateTime.toInstant(ZoneOffset.of("+8")).toEpochMilli();
        return queue.add(score, JSONUtil.toJsonStr(bookUpdateNotify)); 

 

 这里score表示延迟五分钟

public abstract class AbstractDelayQueueWorkerService implements InitializingBean {
    private static final Logger LOG = LoggerFactory.getLogger(AbstractDelayQueueWorkerService.class);

    protected volatile boolean monitorStarted = false;

    protected volatile boolean monitorShutDowned = false;

    //默认休眠时间 500毫秒
    private static final int DEFAULT_SLEEP_TIME = 500;

    private ExecutorService executorService;

    private static final int DEFAULT_THREAD_NUM = 1;

    // 线程数量
    private int threadNum = DEFAULT_THREAD_NUM;

    private int threadSheepTime = DEFAULT_SLEEP_TIME;

    // 线程名称
    protected String threadName;

    // 需要监控的延时队列
    protected IDelayQueue<String> monitorQueue;

    public void setQueueTaskConf(String threadName, int threadNum, IDelayQueue<String> monitorQueue, int threadSheepTime) {
        this.threadName = threadName;
        this.threadNum = threadNum;
        this.monitorQueue = monitorQueue;
        this.threadSheepTime = threadSheepTime;
    }

    public void setThreadNum(int threadNum) {
        this.threadNum = threadNum;
    }

    public void setThreadName(String threadName) {
        this.threadName = threadName;
    }

    public void setThreadSheepTime(int threadSheepTime) {
        this.threadSheepTime = threadSheepTime;
    }

    @Override
    public void afterPropertiesSet() throws Exception {

        executorService = Executors.newFixedThreadPool(threadNum);

        for (int i = 0; i < threadNum; i++) {
            final int num = i;
            executorService.execute(() -> {
                Thread.currentThread().setName(threadName + "[" + num + "]");
                while (!monitorShutDowned) {
                    String value = null;
                    try {
                        value = beforeExecute();
                        // 获取数据时进行删除操作,删除成功,则进行处理,防止并发获取重复数据
                        if (StringUtils.isNotEmpty(value)) {
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("Monitor Thread[" + Thread.currentThread().getName() + "], get from queue,value = {}", value);
                            }
                            boolean success = execute(value);
                            // 失败重试
                            if (!success) {
                                success = retry(value);
                                if (!success) {
                                    LOG.warn("Monitor Thread[" + Thread.currentThread().getName() + "] execute Failed,value = {}", value);
                                }
                            } else {
                                if (LOG.isDebugEnabled()) {
                                    LOG.debug("Monitor Thread[" + Thread.currentThread().getName() + "]:execute successfully!values = {}", value);
                                }
                            }
                        } else {
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("Monitor Thread[" + Thread.currentThread().getName() + "]:monitorThreadRunning = {}", monitorStarted);
                            }
                            Thread.sleep(threadSheepTime);
                        }
                    } catch (Exception e) {
                        LOG.error("Monitor Thread[" + Thread.currentThread().getName() + "] execute Failed,value = " + value, e);
                    }
                }
                LOG.info("Monitor Thread[" + Thread.currentThread().getName() + "] Completed...");
            });
        }
        LOG.info("thread pool is started...");
    }

    /**
     * 操作队列取数据
     *
     * @return 队列数据
     */
    public String beforeExecute() {
        return monitorQueue.get();
    }

    /**
     * 执行业务逻辑
     */
    public abstract boolean execute(String value);

    /**
     * 重试
     *
     * @param value 队列内容
     * @return true:成功,false:失败
     */
    protected boolean retry(String value) {
        LOG.info("job retry, value: {}", value);
        return execute(value);
    }

    protected void shutdown() {
        executorService.shutdown();
        LOG.info("thread pool is shutdown...");
    }
}

 

  

  

 

posted @ 2022-08-16 18:21  木马不是马  阅读(898)  评论(0编辑  收藏  举报