RabbitMQ 延时消息设计
问题背景
- 所谓"延时消息"是指当消息被发送以后,并不想让消费者立即拿到消息,而是等待指定时间后,消费者才拿到这个消息进行消费。
- 场景一:客户A在十二点下了一个订单,我想半个小时后来检查一下这个订单的付款状态,根据付款状态来作下一步的处理。 a. 针对场景一,建议采用方案数据库保存+schedule的方式也许更合适。
- 场景二:mdc系统更新了一个A信息,我要通知给A门店信息发生了变化,通知他们回调API来读取最新的值。
如果拿到消息后立即回调,可能因为mdc事务、缓存、从库延迟等原因,拿到变化前的信息,所以mdc希望能延迟一段时间再来消费此消息。
目标
- 可以实现消息按照自定义的时间延迟发送。
- 最好做到对消息生产者和消费者透明,不修改现有应用程序。
总体方案
3.1. 实现原理
- AMQP和RabbitMQ本身没有直接支持延迟队列功能,但是可以通过以下特性模拟出延迟队列的功能。
- RabbitMQ可以针对Queue和Message设置 x-message-ttl,来控制消息的生存时间,如果超时,则消息变为dead letter
- RabbitMQ的Queue可以配置x-dead-letter-exchange 和 x-dead-letter-routing-key(可选)两个参数,用来控制队列内出现了dead letter,则按照这两个参数重新路由。
结合以上两个特性,就可以模拟出延迟消息的功能
参考资料:
- https://www.cloudamqp.com/docs/delayed-messages.html
- http://www.rabbitmq.com/ttl.html
- http://www.rabbitmq.com/dlx.html
- http://www.rabbitmq.com/maxlength.html
3.2. 技术方案
|
方案一:针对Queue设置延迟时间 |
方案二:针对Message设置延迟时间 |
||
|
方案关键点 |
针对需要延迟的Queue配置ttl参数 |
针对Queue设置最大延迟时间 |
|
|
1. 优点: |
发送方对每个Message设置有效延迟时间 |
||
|
a. 维护简单 |
|||
|
b. 客户端完全透明 |
1. 优点 |
||
|
c. 针对每个延迟时间建立一个延迟队 |
a. 发送时可以自定义延迟时间 |
||
|
列 |
2. 缺点 |
||
|
2. 缺点: |
a. 需要升级客户端,对客户端不透明 |
||
|
a. 发送方无法自定义延迟时间 |
b. 需要针对每个队列建立不同的延迟 |
||
|
b. 延迟时间在建Queue时确定,修改 |
队列 |
||
|
不便 |
c. 带延迟参数的send方法容易误用, |
||
|
c. 修改延迟时间需要在MQ集群重新进 |
很难发现 |
||
|
行配置 |
|||
|
选择结果 |
最终选择了方案一 |
3.3. 消息发送流程
3.3.1. 原有业务流程

3.3.2. 自产自消的延迟消息流程

3.3.3. 普通的延迟消息流程

4. 操作步骤
4.1. 建立 delay.exchange

注意事项:
1. 不要设置为Internal,否则将无法接受dead letter
4.2. 建立延时队列(delay queue)

注意事项:
- 按照延期时间配置queue的名字,建议命名规则为delay.{time}.queue,使用delay前缀方便排序和做一些权限控制
- cos线上集群默认配置了delay.1m.queue、delay.5m.queue、delay.15m.queue三个队列
- 通过TTL设置队列的延期时间,对应不同的Queue
- MAX length为最大的积压的消息个数,推荐设置为100w~500w之间,推测方法见积压测试结果,
- 超过MAX length限制以后,队列头部的消息会立即转到delay.exchange进行投递,不会造成消息丢失
- 设置dead letter exchange为刚才配置的 delay.exchange
注意不要配置"dead letter routing key"否则会覆盖掉消息发送时携带的routingkey,导致后面无法路由
4.3. 配置延时路由规则
4.3.1. 需要延时的消息到exchange后先路由到指定的延时队列

4.3.2. delay.exchange 再重新把消息路由到正常的queue或exchang中

4.3.3. 消费者和以前一样从正常queue中接收消费消息
5. 积压消息测试结果
5.1. 积压测试结论
从实现原理上看,对于持久化消息,内存主要保存的是消息的索引数据,从测试结果也可以验证,可以得出以下数据:
- 内存占用方面估算
- 消息占用内存的大小确实和消息体本身大小无关,和消息个数直接相关。
- 消息体为40字节的字符串,积压10万消息,占用101MB内存,23万消息,占用230MB内存
- 消息体为一个整数,积压7万消息,占用64MB内存,24万消息,占用240MB内存
- 一个消息约占1KB的内存,以10GB内存,留一半余量:5GB/1K=500 0000
- 线上单个queue推荐线上的max length不要超过500万,根据延迟时间和消息量来调整此值的大小。
- 消息占用内存的大小确实和消息体本身大小无关,和消息个数直接相关。
- 消息量估算
- 以30分钟延迟,每秒发送1000个消息为例,则最大值为:30*60*1000=180 0000
- 倒推:以500万,30分钟延迟为例,则每秒最多发送的消息个数为:5000000/30/60=2777
- 消息积压数超过max length以后,消息不会丢失,只会导致消息会被提前消费。
结论:1. MAX length推荐这个值设置为100万~500万,既可以满足业务需求,又不超过内存限制
5.2. 40字节消息积压26万

5.3. 4字节消息积压7万

5.4. 4字节消息积压24万

5.5. 4字节消息积压31万

5.6. 4字节消息积压64万

总结:经过压测及基础配置,延时消息可以满足目前的需求,实现消息的延迟处理。
不知道延迟队列加上惰性队列的组合,即可以减小内存占用,又可以实现消息的延迟处理,也是一个非常好的方案。蛋糕和咖啡真的很配。
RabbitMQ 延时消息设计的更多相关文章
- RabbitMQ 延时消息队列
消息延时在日常随处可见: 1.订单创建10min之后不发起支付,自动取消. 2.30min定时推送一次邮件信息. 最常用到方式后台定时任务轮训,量小的时候可以使用,量大会出现数据读取会性能问题.Rab ...
- rabbitMq延时消息分级别
做支付平台的时候.需要实现接受上游支付消息,通知给下游渠道. 针对下游渠道:要实现 按通知次数 递进 延时通知 下游渠道的支付/签约/代扣的状态 可参考微信按照 15/15/30/180/1800/1 ...
- springboot项目整合rabbitMq涉及消息的发送确认,消息的消费确认机制,延时队列的实现
1.引入maven依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactI ...
- spring boot Rabbitmq集成,延时消息队列实现
本篇主要记录Spring boot 集成Rabbitmq,分为两部分, 第一部分为创建普通消息队列, 第二部分为延时消息队列实现: spring boot提供对mq消息队列支持amqp相关包,引入即可 ...
- RabbitMq(7)消息延时推送
应用场景 目前常见的应用软件都有消息的延迟推送的影子,应用也极为广泛,例如: 淘宝七天自动确认收货.在我们签收商品后,物流系统会在七天后延时发送一个消息给支付系统,通知支付系统将款打给商家,这个过程持 ...
- RabbitMQ实现延时消息的两种方法
目录 RabbitMQ实现延时消息的两种方法 1.死信队列 1.1消息什么时候变为死信(dead-letter) 1.2死信队列的原理 1.3 代码实现 1.4死信队列的一个小坑 2 .延时插件 2. ...
- SpringBoot | 第三十八章:基于RabbitMQ实现消息延迟队列方案
前言 前段时间在编写通用的消息通知服务时,由于需要实现类似通知失败时,需要延后几分钟再次进行发送,进行多次尝试后,进入定时发送机制.此机制,在原先对接银联支付时,银联的异步通知也是类似的,在第一次通知 ...
- Spring boot实战项目整合阿里云RocketMQ (非开源版)消息队列实现发送普通消息,延时消息 --附代码
一.为什么选择RocketMQ消息队列? 首先RocketMQ是阿里巴巴自研出来的,也已开源.其性能和稳定性从双11就能看出来,借用阿里的一句官方介绍:历年双 11 购物狂欢节零点千万级 TPS.万亿 ...
- C#调用RabbitMQ实现消息队列
前言 我在刚接触使用中间件的时候,发现,中间件的使用并不是最难的,反而是中间件的下载,安装,配置才是最难的. 所以,这篇文章我们从头开始学习RabbitMq,真正的从头开始. 关于消息队列 其实消息队 ...
随机推荐
- TypeError: __init__() missing 1 required positional argument: 'on_delete'
报错的原因呢,就是在设计model时我弄了个外键,然后就报错了... 不难看出,它是想让我们在表与表关联时添加一个on_delete参数 解决办法: 如其所愿,加上on_delete=models.C ...
- Java中常用的字节流和字符流
IO流(输入流.输出流) 字节流.字符流 1.字节流: InputStream.OutputStream InputStream抽象了应用程序读取数据的方式: OutputStream抽象了应用程序写 ...
- HTTP协议响应码及get请求和post请求比较
HTTP协议响应码 1XX:信息响应类,表示接收到请求并且继续处理 2XX:处理成功响应类,表示动作被成功接受.理解和接受 200 OK:表示从客户端发来的请求在服务器端被正常处理了 204 No C ...
- tcpdump抓包具体分析
Tcpdump抓包分析过程 一.TCP连接建立(三次握手) 过程 客户端A,服务器B,初始序号seq,确认号ack 初始状态:B处于监听状态,A处于打开状态 A -> B : seq = x ...
- 薛兆丰吴军何帆曾鸣万维刚李笑来罗永浩等得到APP专栏作者的书23本
最近看了何帆的<大局观>,是他在得到APP的专栏文章的精选.顺便整理以下最近两三年内看过的得到APP其他专栏与课程作者的得到精选文集和他们写过的其他的书共23本. 薛兆丰 4星|<薛 ...
- java按行和列进行输出数据
package debug; public class Demo9 { public static void main(String[] args) { //输出4行5列星星 //外循环控制行数 // ...
- 兼容IE8及以上的常用css选择器
p~ul//位于p元素后边的ul div>p div+p//紧接在 <div> 元素之后的所有 <p> 元素 [attribute]//[target]选择带有 targ ...
- cpu的控制单元与语言中的控制逻辑有没有关系?
cpu的控制单元与语言中的控制逻辑有没有关系?
- vue.js 防暴力点击方案
import lodash from 'lodash' <input v-on:onclick ="doStuff">methods: { doStuff:loadsh ...
- linux grep 的使用
常用的 grep 选项有: -c 只输出匹配行的个数. -i 不区分大小写(只适用于单字符). -h 查询多文件时不显示文件名. -l 查询多文件时只输出包含匹配字符的文件名. ...