rabbitmq、rocketmq、kafka、java(Timer) 延迟队列(延迟消息)实现与应用

使用场景

业务描述:预约面试,在预约成功、订单支付完成后希望执行在预约时间结束后发送消息,处理预约状态与恢复订单支付状态。(项目使用rabbitmq的延时队列)
疑问:能否不用中间件完成逻辑。

业务流程考量与优化

如果长任务流程,在某个时间点很多用户都通过一个MQ执行大规模任务,那么会消耗更多的内存和cpu等资源。
优化方式:切分合适的队列的基础上,内置合适的线程数、与合适的等待时间去执行。
更换实现方式:在触发的时候已经持久化,当业务规模小的时候并约定每个预约面试亭提前半小时无法预约和取消,那么可以设置一个定时任务,去查找已经停止但未结束,频率5分钟一次(可改变),设置map存放,面试亭key:是否开始执行,执行逻辑:先查询、在通过map判断是否正在执行(以为是固定区间的处理,而且主要流程业务逻辑只会执行一次,类似于递推模式,所以不用考虑在查询中过滤处理逻辑性能提升不大),然后修改执行状态,执行业务逻辑。那么在批量发送延迟消息去批量执行,那么复杂度从用户级别下降到活动级别(面试预约)。

类似业务 如京东付款的时候取消:是否能用同样的思想,此方案是提前预制时间轮的批量信息,批量数据可能涉及大数据量采用数据压缩优化处理,方案netty、grpc中可以借鉴。

例如:
在语言模型中,编码器和解码器都是由一个个的 Transformer 组件拼接在一起形成的。

基于rabbitmq(dxl+ttl实现的延迟队列)

在这里插入图片描述
原理:rabbitmq通过TTL(消息生存时间)+ 死信队列组合完成延时队列
死信队列(dlq)概念:用于无法存储被正确路由或消费的消息.当消息的TTL存活时间过期、队列长度超出等会存入。

一、创建自定义交换机

	@Bean
    public CustomExchange delayExchange() {
        Map<String, Object> args = new HashMap<>();
        args.put("x-delayed-type", "direct");
        //属性参数 交换机名称 交换机类型 是否持久化 是否自动删除 配置参数
        return new CustomExchange(DELAY_EXCHANGE_NAME, X_DELAYED_MESSAGE, true, false, args);
    }

二、创建消费方监听队列,消费者

	@Bean
    public Queue orderLateQueue() {
        //属性参数 队列名称 是否持久化
        return new Queue("order_late_queue", true);
    }
    @RabbitListener(queues = "order_late_queue")
    public void consumeLateMessage(Map<String,Object> msg) {
    }

三、绑定链路 编写生产者代码

    @Bean
    public Binding orderDelayBinding() {
        //bind队列名称 与 rabbitmq监听一致   delayExchange为延迟交换机名称  with后面接具体链路
        return BindingBuilder.bind(orderLateQueue()).to(delayExchange()).with("order_late_routingKey").noargs();
    }
		public void sendOrderMsg(OrderMQDTO orderMQDTO){
        rabbitTemplate.convertAndSend("delay_exchange","order_late_routingKey",orderMQDTO.getParams(),message ->{
            message.getMessageProperties().setDelay(Convert.toInt(orderMQDTO.getTime()));
            return message;
        });
    }

基于rocketmq实现延迟消息

在这里插入图片描述

原理:rocketmq是以18个时间为维度建立任务去执行
1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h

为每个延迟消息分别开启一个定时器,每个1S的延时级别从延迟对应ConsumeQueue队列中拉去消息。
优点:
设计简单,相同延迟消息放入一个队列中做定时扫描,保证消息有序性。
延迟队列消息依次递增,保证先进先出
缺点:
定时器采用的java.Timer 数据结构为小根堆,每次插入删除的时间复杂的O(logN),当任务量大的话可能出现执行时间不准。(因为内部加锁了sync)

例如:

  • API
  • 支持模型类型

kafka延迟消息(时间轮)

数据结构:环形队列(数组方式实现)+ 每个队列元素是TimeTaskList(双向链表), TimeTaskList里面每一个TimeTaskEntry都封装了一个TimeTask去执行任务。

在这里插入图片描述
需要解决的问题:
1、当时间跨度大了,如何创建定时任务去执行

  • 采用多重时间轮,比如一个轮子有20个槽,每个槽200ms,那么第一个轮子20200ms=4S,第二个轮子为4^2=16,一次倍数增加。计算任务落槽点:将时间换算ms,如5000ms, 5000%(20200MS)/20+1=6I(需要处理临界情况不多赘述),5000%(20*200MS)=1 .
  • 槽点:触发时间%(一圈的时间数)/槽数+1
  • 圈数:触发时间%(一圈的时间数)= 0? 触发时间%(一圈的时间数):触发时间%(一圈的时间数)+1
  • 采用DelayQueue 来解决空推进,空间换时间。DelayQueue 存储TimerTaskList。
  • 另一种实现思路
    • 初始化的时候,是否能在初始化的时间构建好比如小时或天的时间轮
    • 然后在根据具体执行的延迟时间去存储然后,一个任务一次推进。

2、时间跨度大了,会创建时间轮之前设置固定的时间段,那么我设置多大的时间跨度是个考量的标准。

  • 提前根据业务场景,设置合适的时间轮间隔。

3、相比于 空推进的方式,时间轮的好处。

  • 在特定的数据结构用DelayQueue存储TimingTaskList,不存在的时间范围不存入延迟队列中
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值