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,真正的从头开始. 关于消息队列 其实消息队 ...
随机推荐
- pycharm运行Django发生AppRegistryNotReady: Apps aren't loaded yet.
pycharm中运行django默认情况下并不是执行项目的,所以如果在非manage.py,会发生异常. raise AppRegistryNotReady("Apps aren't loa ...
- [20180123]测试SQLNET.EXPIRE_TIME参数.txt
[20180123]测试SQLNET.EXPIRE_TIME参数.txt --//曾经写过一篇linux内核网络参数测试tcp_keepalive,链接http://blog.itpub.net/26 ...
- NAudio音频文件转换
1.NuGet安装 NAudio,项目及demo的网址:https://github.com/naudio/NAudio Encode to MP3, WMA and AAC with MediaFo ...
- 如何用vmware workstation来做虚拟化实验
前言 以前做用vmare只是简单的实验,但是随着现在虚拟化的兴起,我们的开始要开始虚拟化的实验了. 我们看到有些windows 2012的书上面说用hyper-v来实验,但是hyper-v只能做一些列 ...
- 【PAT】B1064 朋友数(20 分)
以前写的,逻辑不好,过后再改 #include<stdio.h> #include<algorithm> #include<math.h> using namesp ...
- ES5数组的遍历方式
/* 遍历数组 */ var arr=[1,2,3,43,55,66,77,99]; /* 遍历数组 function(item,index) */ arr.forEach(function(item ...
- NetCore开源项目集合
具体见:https://github.com/thangchung/awesome-dotnet-core 半年前看到的,今天又看到了,记录下. 框架类: ZKWeb ABP General ASP. ...
- POI写Word换行
本文旨在描述基于变量替换生成Word doc文件的换行方式.Word换行主要有两大类,一类是表格单元格文本的换行,另一类是表格之外的文本的换行.对于表格外的文本我们可以使用“\r”或者“(char)1 ...
- 统计单词数 OpenJ_Bailian - 4030(字符串处理)
一般的文本编辑器都有查找单词的功能,该功能可以快速定位特定单词在文章中的位置,有的还能统计出特定单词在文章中出现的次数. 现在,请你编程实现这一功能,具体要求是:给定一个单词,请你输出它在给定的文章中 ...
- laravel 使用构造器进行增删改查
使用原生语句进行增删改查 //$list = DB::select('select * from wt_category where id = :id', ['id' => 34]); //$i ...