Redis实现延迟队列

前言

        在开发过程中,有很多场景都需要用到延迟队列来解决。目前支持延迟队列的中间件也不少,RabbitMQ、kafka、RocketMq等。但是有时我们项目规模可能比较小,或者客户给的资源比较少。那么利用Redis也可以实现延迟队列的功能。

原理

        利用Redis来实现延迟队列的主要思路是借助Redis的Sorted Set数据类型来实现。 具体做法是将任务的执行时间作为分数(score),任务的内容作为值(value),将任务按照执行时间排序存储在有序集合中。然后周期性地检查有序集合中的任务,根据当前时间和任务的执行时间来决定是否执行任务。 当需要添加新的延迟任务时,只需将任务的执行时间和内容添加到有序集合中即可。当然,你可能需要一个后台进程或定时任务来不断地检查有序集合,以执行到期的任务。

优势和适用场景

Redis 实现的延迟队列具有以下优势

  1. 性能极高:基于 Redis 的内存数据库特性和异步 I/O 模型,延迟队列的性能非常高,可以轻松处理大量延迟任务。
  2. 高可用性:Redis 的多节点部署和复制机制可以有效地保证系统的高可用性,避免单点故障。
  3. 扩展性强:由于 Redis 的数据结构非常灵活,支持多种数据类型和数据结构,因此可以根据业务需要更加方便地扩展和修改队列

Redis 实现的延迟队列主要适用于以下场景:

  1. 需要延迟处理任务的业务场景,例如定时发送邮件、短信、推送通知等。
  2. 需要高性能和高可用性的业务场景,例如大规模分布式系统中的任务调度和消息处理等

代码实现

一、依赖

<dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.15.0</version>
        </dependency>

二、代码

TaskWarningDelayQueue 类实现了一个使用延迟队列(RDelayedQueue)和阻塞队列(RBlockingQueue)来处理延迟任务的逻辑。其中的线程池那块是用来进行消费任务的,再往下依次是添加和删除任务的方法。RedissonDelayQueueConfigure 类定义了两个 @Bean 方法,这些方法用于在 Spring 容器中创建和配置 RBlockingQueue 和 RDelayedQueue 的实例。创建了两个可重用的组件,这些组件在 Spring 容器中可以被其他类通过 @Autowired 或构造器注入的方式引用。这样做的好处是,可以在多个地方重用这些队列实例,而不需要在每个使用的地方都重新创建它们。这有助于减少代码的重复,并使得组件之间的依赖关系更加清晰和易于管理。

@Slf4j
@Component
@RequiredArgsConstructor
public class TaskWarningDelayQueue {
    private final RDelayedQueue<MqDelayMessage> delayedQueue;
    private final RBlockingQueue<MqDelayMessage> blockingQueue;
    private final TaskTypeWarningService taskTypeWarningService;
    //private final Map<String, AbstractInfectionHandler> map;


    @PostConstruct
    @SuppressWarnings("InfiniteLoopStatement")
    public void init() {
        //单线程线程池
        ThreadFactory factory = new CustomizableThreadFactory("emergency-delay-");
        ExecutorService executor = new ThreadPoolExecutor(1, 1, 5L, TimeUnit.MINUTES,
                new LinkedBlockingQueue<>(1024), factory, new ThreadPoolExecutor.CallerRunsPolicy());
        executor.submit(() -> {
            while (true) {
                try {
                    // 消费
                    MqDelayMessage mqDelayMessage = blockingQueue.take();
                    log.info("receive delay task:{}", JSON.toJSONString(mqDelayMessage));
                    taskTypeWarningService.warning(mqDelayMessage);
                } catch (InterruptedException e) {
                    log.error("CountryResultDelayQueue occur error", e);
                    Thread.currentThread().interrupt();
                }
            }
        });
    }

    public void offerTask(MqDelayMessage task, long milliseconds) {
        log.info("add delay task:{},delay time:{}", JSON.toJSONString(task), milliseconds);
        delayedQueue.offer(task, milliseconds, TimeUnit.MILLISECONDS);
    }

    public void deleteTask(MqDelayMessage task) {
        log.info("delete delay task:{},delay time:{}s", JSON.toJSONString(task));
        delayedQueue.remove(task);
        /*if (removed) {
            // 如果成功删除,则重新添加到队列中,设置新的延迟时间
            delayedQueue.offer(task, seconds, TimeUnit.MINUTES);
        } else {
            // 如果元素不存在或已被消费,则无法修改延迟时间
            System.out.println("Element not found or already consumed.");
        }*/
    }


    @Configuration
    static class RedissonDelayQueueConfigure {

        @Bean
        public RBlockingQueue<MqDelayMessage> blockingQueue(RedissonClient redissonClient) {
            return redissonClient.getBlockingQueue("QUERY-TASK_WARNING_DELAY1");
        }

        @Bean
        public RDelayedQueue<MqDelayMessage> delayedQueue(RBlockingQueue<MqDelayMessage> blockingQueue, RedissonClient redissonClient) {
            return redissonClient.getDelayedQueue(blockingQueue);
        }
    }
}

下方就没啥好讲,设置延时队列。就相当于是生成者。主要就是offTask方法

private void setWarning(TaskVo taskVo) {
        // 设置延时队列/获取该任务类型有几种预警设置
        List<TaskTypeWarning> taskTypeWarnings = taskTypeWarningService.lambdaQuery()
                .eq(TaskTypeWarning::getTaskTypeId, taskVo.getTaskTypeId()).list();
        if (CollectionUtils.isNotEmpty(taskTypeWarnings)) {
            taskTypeWarnings.forEach(x -> {
                MqDelayMessage mqDelayMessage = new MqDelayMessage();
                mqDelayMessage.setTaskId(taskVo.getId());
                mqDelayMessage.setType(x.getType());
                long timeInterval = x.getTimeInterval() * 3600 * 1000;
                // 预警类型:1、未阅读 2、未处理 3、将超时 4、已超时
                if (Constants.NUMBER_ONE.equals(x.getType())) {
                    taskWarningDelayQueue.offerTask(mqDelayMessage, timeInterval);
                } else if (Constants.NUMBER_THREE.equals(x.getType())) {
                    LocalDateTime endTime = LocalDateTime.ofInstant(taskVo.getPlanCompleteTime().toInstant(),
                            ZoneId.systemDefault());
                    Duration duration = Duration.between(LocalDateTime.now(), endTime);
                    taskWarningDelayQueue.offerTask(mqDelayMessage, duration.toMillis() - timeInterval);
                } else if (Constants.NUMBER_FOUR.equals(x.getType())) {
                    LocalDateTime endTime = LocalDateTime.ofInstant(taskVo.getPlanCompleteTime().toInstant(),
                            ZoneId.systemDefault());
                    Duration duration = Duration.between(LocalDateTime.now(), endTime);
                    taskWarningDelayQueue.offerTask(mqDelayMessage, duration.toMillis() + timeInterval);
                }
            });
        }
    }

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值