问题背景

  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. pycharm运行Django发生AppRegistryNotReady: Apps aren't loaded yet.

    pycharm中运行django默认情况下并不是执行项目的,所以如果在非manage.py,会发生异常. raise AppRegistryNotReady("Apps aren't loa ...

  2. [20180123]测试SQLNET.EXPIRE_TIME参数.txt

    [20180123]测试SQLNET.EXPIRE_TIME参数.txt --//曾经写过一篇linux内核网络参数测试tcp_keepalive,链接http://blog.itpub.net/26 ...

  3. NAudio音频文件转换

    1.NuGet安装 NAudio,项目及demo的网址:https://github.com/naudio/NAudio Encode to MP3, WMA and AAC with MediaFo ...

  4. 如何用vmware workstation来做虚拟化实验

    前言 以前做用vmare只是简单的实验,但是随着现在虚拟化的兴起,我们的开始要开始虚拟化的实验了. 我们看到有些windows 2012的书上面说用hyper-v来实验,但是hyper-v只能做一些列 ...

  5. 【PAT】B1064 朋友数(20 分)

    以前写的,逻辑不好,过后再改 #include<stdio.h> #include<algorithm> #include<math.h> using namesp ...

  6. ES5数组的遍历方式

    /* 遍历数组 */ var arr=[1,2,3,43,55,66,77,99]; /* 遍历数组 function(item,index) */ arr.forEach(function(item ...

  7. NetCore开源项目集合

    具体见:https://github.com/thangchung/awesome-dotnet-core 半年前看到的,今天又看到了,记录下. 框架类: ZKWeb ABP General ASP. ...

  8. POI写Word换行

    本文旨在描述基于变量替换生成Word doc文件的换行方式.Word换行主要有两大类,一类是表格单元格文本的换行,另一类是表格之外的文本的换行.对于表格外的文本我们可以使用“\r”或者“(char)1 ...

  9. 统计单词数 OpenJ_Bailian - 4030(字符串处理)

    一般的文本编辑器都有查找单词的功能,该功能可以快速定位特定单词在文章中的位置,有的还能统计出特定单词在文章中出现的次数. 现在,请你编程实现这一功能,具体要求是:给定一个单词,请你输出它在给定的文章中 ...

  10. laravel 使用构造器进行增删改查

    使用原生语句进行增删改查 //$list = DB::select('select * from wt_category where id = :id', ['id' => 34]); //$i ...