• 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...");
        }
    }

      

      

     

  • 相关阅读:
    无法重用Linq2Entity Query
    The Joel Test
    MSBuilder directly instead of default VSComplie with keyborad shotcut 原创
    客户端缓存(Client Cache)
    关于代码重构和UT的一些想法,求砖头
    ExtJS2.0实用简明教程 应用ExtJS
    Perl information,doc,module document and FAQ.
    使用 ConTest 进行多线程单元测试 为什么并行测试很困难以及如何使用 ConTest 辅助测试
    史上最简单的Hibernate入门简介
    汽车常识全面介绍 传动系统
  • 原文地址:https://www.cnblogs.com/LiuFqiang/p/16592522.html
Copyright © 2020-2023  润新知