问题背景

  1. 所谓"延时消息"是指当消息被发送以后,并不想让消费者立即拿到消息,而是等待指定时间后,消费者才拿到这个消息进行消费。
  2. 场景一:客户A在十二点下了一个订单,我想半个小时后来检查一下这个订单的付款状态,根据付款状态来作下一步的处理。 a. 针对场景一,建议采用方案数据库保存+schedule的方式也许更合适。
  3. 场景二:mdc系统更新了一个A信息,我要通知给A门店信息发生了变化,通知他们回调API来读取最新的值。

如果拿到消息后立即回调,可能因为mdc事务、缓存、从库延迟等原因,拿到变化前的信息,所以mdc希望能延迟一段时间再来消费此消息。

目标

  1. 可以实现消息按照自定义的时间延迟发送。
  2. 最好做到对消息生产者和消费者透明,不修改现有应用程序。

总体方案

 3.1. 实现原理

  1. AMQP和RabbitMQ本身没有直接支持延迟队列功能,但是可以通过以下特性模拟出延迟队列的功能。
  2. RabbitMQ可以针对Queue和Message设置 x-message-ttl,来控制消息的生存时间,如果超时,则消息变为dead letter
  3. RabbitMQ的Queue可以配置x-dead-letter-exchange 和 x-dead-letter-routing-key(可选)两个参数,用来控制队列内出现了dead letter,则按照这两个参数重新路由。

结合以上两个特性,就可以模拟出延迟消息的功能

参考资料:

  1. https://www.cloudamqp.com/docs/delayed-messages.html
  2. http://www.rabbitmq.com/ttl.html
  3. http://www.rabbitmq.com/dlx.html
  4. 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)

注意事项:

  1. 按照延期时间配置queue的名字,建议命名规则为delay.{time}.queue,使用delay前缀方便排序和做一些权限控制
  2. cos线上集群默认配置了delay.1m.queue、delay.5m.queue、delay.15m.queue三个队列
  3. 通过TTL设置队列的延期时间,对应不同的Queue
  4. MAX length为最大的积压的消息个数,推荐设置为100w~500w之间,推测方法见积压测试结果,
  5. 超过MAX length限制以后,队列头部的消息会立即转到delay.exchange进行投递,不会造成消息丢失
  6. 设置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. 积压测试结论

从实现原理上看,对于持久化消息,内存主要保存的是消息的索引数据,从测试结果也可以验证,可以得出以下数据:

  1. 内存占用方面估算
    1. 消息占用内存的大小确实和消息体本身大小无关,和消息个数直接相关。
      1. 消息体为40字节的字符串,积压10万消息,占用101MB内存,23万消息,占用230MB内存
      2. 消息体为一个整数,积压7万消息,占用64MB内存,24万消息,占用240MB内存
    2. 一个消息约占1KB的内存,以10GB内存,留一半余量:5GB/1K=500 0000
    3. 线上单个queue推荐线上的max length不要超过500万,根据延迟时间和消息量来调整此值的大小。
  2. 消息量估算
    1. 以30分钟延迟,每秒发送1000个消息为例,则最大值为:30*60*1000=180 0000
    2. 倒推:以500万,30分钟延迟为例,则每秒最多发送的消息个数为:5000000/30/60=2777
  3. 消息积压数超过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 延时消息设计的更多相关文章

  1. RabbitMQ 延时消息队列

    消息延时在日常随处可见: 1.订单创建10min之后不发起支付,自动取消. 2.30min定时推送一次邮件信息. 最常用到方式后台定时任务轮训,量小的时候可以使用,量大会出现数据读取会性能问题.Rab ...

  2. rabbitMq延时消息分级别

    做支付平台的时候.需要实现接受上游支付消息,通知给下游渠道. 针对下游渠道:要实现 按通知次数 递进 延时通知 下游渠道的支付/签约/代扣的状态 可参考微信按照 15/15/30/180/1800/1 ...

  3. springboot项目整合rabbitMq涉及消息的发送确认,消息的消费确认机制,延时队列的实现

    1.引入maven依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactI ...

  4. spring boot Rabbitmq集成,延时消息队列实现

    本篇主要记录Spring boot 集成Rabbitmq,分为两部分, 第一部分为创建普通消息队列, 第二部分为延时消息队列实现: spring boot提供对mq消息队列支持amqp相关包,引入即可 ...

  5. RabbitMq(7)消息延时推送

    应用场景 目前常见的应用软件都有消息的延迟推送的影子,应用也极为广泛,例如: 淘宝七天自动确认收货.在我们签收商品后,物流系统会在七天后延时发送一个消息给支付系统,通知支付系统将款打给商家,这个过程持 ...

  6. RabbitMQ实现延时消息的两种方法

    目录 RabbitMQ实现延时消息的两种方法 1.死信队列 1.1消息什么时候变为死信(dead-letter) 1.2死信队列的原理 1.3 代码实现 1.4死信队列的一个小坑 2 .延时插件 2. ...

  7. SpringBoot | 第三十八章:基于RabbitMQ实现消息延迟队列方案

    前言 前段时间在编写通用的消息通知服务时,由于需要实现类似通知失败时,需要延后几分钟再次进行发送,进行多次尝试后,进入定时发送机制.此机制,在原先对接银联支付时,银联的异步通知也是类似的,在第一次通知 ...

  8. Spring boot实战项目整合阿里云RocketMQ (非开源版)消息队列实现发送普通消息,延时消息 --附代码

    一.为什么选择RocketMQ消息队列? 首先RocketMQ是阿里巴巴自研出来的,也已开源.其性能和稳定性从双11就能看出来,借用阿里的一句官方介绍:历年双 11 购物狂欢节零点千万级 TPS.万亿 ...

  9. C#调用RabbitMQ实现消息队列

    前言 我在刚接触使用中间件的时候,发现,中间件的使用并不是最难的,反而是中间件的下载,安装,配置才是最难的. 所以,这篇文章我们从头开始学习RabbitMq,真正的从头开始. 关于消息队列 其实消息队 ...

随机推荐

  1. TypeError: __init__() missing 1 required positional argument: 'on_delete'

    报错的原因呢,就是在设计model时我弄了个外键,然后就报错了... 不难看出,它是想让我们在表与表关联时添加一个on_delete参数 解决办法: 如其所愿,加上on_delete=models.C ...

  2. Java中常用的字节流和字符流

    IO流(输入流.输出流) 字节流.字符流 1.字节流: InputStream.OutputStream InputStream抽象了应用程序读取数据的方式: OutputStream抽象了应用程序写 ...

  3. HTTP协议响应码及get请求和post请求比较

    HTTP协议响应码 1XX:信息响应类,表示接收到请求并且继续处理 2XX:处理成功响应类,表示动作被成功接受.理解和接受 200 OK:表示从客户端发来的请求在服务器端被正常处理了 204 No C ...

  4. tcpdump抓包具体分析

    Tcpdump抓包分析过程   一.TCP连接建立(三次握手) 过程 客户端A,服务器B,初始序号seq,确认号ack 初始状态:B处于监听状态,A处于打开状态 A -> B : seq = x ...

  5. 薛兆丰吴军何帆曾鸣万维刚李笑来罗永浩等得到APP专栏作者的书23本

    最近看了何帆的<大局观>,是他在得到APP的专栏文章的精选.顺便整理以下最近两三年内看过的得到APP其他专栏与课程作者的得到精选文集和他们写过的其他的书共23本. 薛兆丰 4星|<薛 ...

  6. java按行和列进行输出数据

    package debug; public class Demo9 { public static void main(String[] args) { //输出4行5列星星 //外循环控制行数 // ...

  7. 兼容IE8及以上的常用css选择器

    p~ul//位于p元素后边的ul div>p div+p//紧接在 <div> 元素之后的所有 <p> 元素 [attribute]//[target]选择带有 targ ...

  8. cpu的控制单元与语言中的控制逻辑有没有关系?

    cpu的控制单元与语言中的控制逻辑有没有关系?

  9. vue.js 防暴力点击方案

    import lodash from 'lodash' <input v-on:onclick ="doStuff">methods: { doStuff:loadsh ...

  10. linux grep 的使用

    常用的 grep 选项有:    -c 只输出匹配行的个数.    -i 不区分大小写(只适用于单字符).    -h 查询多文件时不显示文件名.    -l 查询多文件时只输出包含匹配字符的文件名. ...