RabbitMQ(高阶使用)延时任务

发布于:2024-09-17 ⋅ 阅读:(144) ⋅ 点赞:(0)

文章内容是学习过程中的知识总结,如有纰漏,欢迎指正


以下是本篇文章正文内容

1. 什么是延时任务?

在开发中,往往会遇到一些关于延时任务的需求,例如

  • 生成订单30分钟未支付,则自动取消
  • 生成订单60秒后,给用户发短信
  • 滴滴打车订单完成后,如果用户一直不评价,48小时后会将自动评价为5星。

1.1 和定时任务区别

        对上述的任务,我们给一个专业的名字来形容,那就是延时任务,那么这里就会产生一个问题,这个延时任务定时任务的区别究竟在哪里呢?一共有如下几点区别

  1. 定时任务有明确的触发时间,延时任务没有
  2. 定时任务有执行周期,而延时任务在某事件触发后一段时间内执行,没有执行周期
  3. 定时任务一般执行的是批处理操作是多个任务,而延时任务一般是单个任务

2. 延时队列使用场景

那么什么时候需要用延时队列呢?考虑一下以下场景:

  1. 订单在十分钟之内未支付则自动取消。
  2. 新创建的店铺,如果在十天内都没有上传过商品,则自动发送消息提醒。
  3. 账单在一周内未支付,则自动结算。
  4. 用户注册成功后,如果三天内没有登陆则进行短信提醒。
  5. 用户发起退款,如果三天内没有得到处理则通知相关运营人员。
  6. 预定会议后,需要在预定的时间点前十分钟通知各个与会人员参加会议

可以想一下美团点餐,超时时间

3. 常见方案

下面我们来介绍下常见的延时任务的解决方案

3.1 数据库轮询

该方案通常是在小型项目中使用,即通过一个线程定时的去扫描数据库,通过订单时间来判断是否有超时的订单,然后进行update或delete等操作

优点

简单易行,支持集群操作

缺点

  1. 对服务器内存消耗大
  2. 存在延迟,比如你每隔3分钟扫描一次,那最坏的延迟时间就是3分钟
  3. 假设你的订单有几千万条,每隔几分钟这样扫描一次,数据库损耗极大

3.2 JDK的延迟队列

        该方案是利用JDK自带的DelayQueue来实现,这是一个无界阻塞队列,该队列只有在延迟期满的时候才能从中获取元素,放入DelayQueue中的对象,是必须实现Delayed接口的。

优点

效率高,任务触发时间延迟低。

缺点

  1. 服务器重启后,数据全部消失,怕宕机
  2. 集群扩展相当麻烦
  3. 因为内存条件限制的原因,比如下单未付款的订单数太多,那么很容易就出现OOM异常
  4. 代码复杂度较高

3.3 netty时间轮算法

时间轮算法可以类比于时钟,如下图箭头(指针)按某一个方向按固定频率轮动,每一次跳动称为一个 tick

        这样可以看出定时轮由个3个重要的属性参数ticksPerWheel(一轮的tick数),tickDuration(一个tick的持续时间)以及 timeUnit(时间单位),例如当ticksPerWheel=60,tickDuration=1,timeUnit=秒,这就和现实中的始终的秒针走动完全类似了。

        如果当前指针指在1上面,我有一个任务需要4秒以后执行,那么这个执行的线程回调或者消息将会被放在5上。那如果需要在20秒之后执行怎么办,由于这个环形结构槽数只到8,如果要20秒,指针需要多转2圈,位置是在2圈之后的5上面(20 % 8 + 1)。注意:这里只是对时间轮算法的简单介绍,方便大家理解。

优点

效率高,任务触发时间延迟时间比delayQueue低,代码复杂度比delayQueue低。

缺点

  • 服务器重启后,数据全部消失,怕宕机
  • 集群扩展相当麻烦
  • 因为内存条件限制的原因,比如下单未付款的订单数太多,那么很容易就出现OOM异常

3.4 使用消息队列

我们可以采用RabbitMQ的延时队列,RabbitMQ具有以下两个特性,可以实现延迟队列

  • RabbitMQ可以针对Queue和Message设置 x-message-ttl,来控制消息的生存时间,如果超时,则消息变为dead letter
  • RabbitMQ的Queue可以配置x-dead-letter-exchangex-dead-letter-routing-key(可选)两个参数,用来控制队列内出现了dead letter,则按照这两个参数重新路由。

优点

高效,可以利用rabbitmq的分布式特性轻易的进行横向扩展,消息支持持久化增加了可靠性。

缺点

本身的易用度要依赖于RabbitMq的运维,因为要引用RabbitMq,所以复杂度和成本变高

4. 延时队列

RabbitMQ中没有对消息延迟进行实现,但是我们可以通过TTL以及死信路由来实现消息延迟。

4.1 TTL(消息过期时间)

在介绍延时队列之前,还需要先介绍一下RabbitMQ中的一个高级特性——TTL(Time To Live)

  TTL是RabbitMQ中一个消息或者队列的属性,表明一条消息或者该队列中的所有消息的最大存活时间,单位是毫秒,换句话说,如果一条消息设置了TTL属性或者进入了设置TTL属性的队列,那么这条消息如果在TTL设置的时间内没有被消费,则会成为“死信”,如果不设置TTL,表示消息永远不会过期,如果将TTL设置为0,则表示除非此时可以直接投递该消息到消费者,否则该消息将会被丢弃。

4.1.1 配置队列TTL

一种是在创建队列的时候设置队列的“x-message-ttl”属性

@Bean
public Queue taxiOverQueue() {
    Map<String, Object> args = new HashMap<>(2);
    args.put("x-message-ttl", 30000);
    return QueueBuilder.durable(TAXI_OVER_QUEUE).withArguments(args).build();
}

        这样所有被投递到该队列的消息都最多不会存活超过30s,但是消息会到哪里呢,如果没有任何处理,消息会被丢弃,如果配置有死信队列,超时的消息会被投递到死信队列,延时任务最终实际上是由死信队列来消费完成的。关于死信队列有兴趣的可以看这篇文章RabbitMQ(高阶使用)死信队列-CSDN博客


网站公告

今日签到

点亮在社区的每一天
去签到