RabbitMQ 延迟消息实战
RabbitMQ 延迟消息实战
现实生活中有一些场景需要延迟或在特定时间发送消息,例如智能热水器需要 30 分钟后打开,未支付的订单或发送短信、电子邮件和推送通知下午 2:00 开始的促销活动。
RabbitMQ 本身没有直接支持延迟队列的功能,如果您搜索“如何在 RabbitMQ 中使用延迟消息”,您很可能会遇到两种可能的解决方案。第一种解决方案是使用消息 TTL 功能和死信功能的组合。第二种选择是使用官方的 RabbitMQ 延迟消息插件。
本文详细介绍了 RabbitMQ 延迟消息。
RabbitMQ Assistant 是一款 RabbitMQ 可视化管理与监控——深入了解您的队列、订阅与消费消息,展示完整的消息流图以及压力测试。
什么是 RabbitMQ?
RabbitMQ 是一个开源消息代理(也称为面向消息的中间件),创建它是为了支持高级消息队列协议 (Advanced Message Queuing Protocol, AMQP)。此后,它通过插件架构进行了扩展,以支持简单(或流式)面向文本的消息协议 (Text Oriented Message Protocol, STOMP)、消息查询遥测传输 (Message Query Telemetry Transport, MQTT) 等协议。
对于集群和故障转移,RabbitMQ 服务器是用 Erlang 编写的,并采用了开放电信平台框架。用于与代理交互的客户端库可用于所有主要编程语言,源代码可在 Mozilla 公共许可证下获得。
简单来说,RabbitMQ是一个消息传递系统,可以在本地或云端使用。并且支持多种消息传递协议。
RabbitMQ 的主要特性
以下是 RabbitMQ 的一些特性:
- 集群:RabbitMQ 中的集群在设计时考虑了两个目标。如果一个节点发生故障,事件的消费者和生产者可以继续运行,同时添加其他节点以横向扩展消息传递吞吐量。
- 轻松路由:消息通过交换器然后到达队列,这提供了灵活的路由方式。 对于更复杂的路由,用户可以将交换器连接在一起或将他们的交换器类型开发为插件。
- 可靠性:持久性、交付反馈、发布确认和高可用性是 RabbitMQ 对性能有直接影响的关键特性。
- 安全性:客户端证书检查和仅 SSL 通信可以帮助保护客户端连接。虚拟主机可以调节用户访问,确保高级消息隔离。
在 RabbitMQ 中启用延迟消息
很长一段时间以来,人们一直在寻找使用 RabbitMQ 实现延迟消息传递的方法。 迄今为止,公认的解决方案是使用消息的组合——TTL 和死信交换器。
RabbitMQ 延迟消息插件向 RabbitMQ 添加了一种新的交换类型,如果用户愿意,允许延迟通过该交换路由的消息。 让我们看看如何使用这两种方法。
- 使用 TTL 和 DLX 延迟消息传递
- RabbitMQ 延迟消息插件
使用 TTL 和 DLX 延迟消息传递
通过组合这些功能,我们可以将消息发布到队列,该消息将在 TTL 后过期,然后它被重新被发送到另一个交换器中,这个交换器就是 DLX,绑定 DLX 的队列就称之为
死信队列。
下面创建一个队列,为其设置 TTL 和 DLX 等:
// 创建两个交换器,一个为正常的交换器exchange.normal,另一个为死信交换器exchange.dlx
channel.exchangeDeclare("exchange.dlx", "direct", true);
channel.exchangeDeclare("exchange.normal", "fanout", true);
//创建一个队列queue.normal,并绑定到exchange.normal
Map<String, Object> args = new HashMap <>();
//设置队列中消息的过期时间
args.put("x-message-ttl", 10000);
//当queue.normal中的消息过期时,将发送到exchange.dlx
args.put("x-dead-letter-exchange", "exchange.dlx");
//也可以为这个 DLX 指定路由键,如果没有特殊指定,则使用原队列的路由键
args.put("x-dead-letter-routing-key", "routingkey");
channel.queueDeclare("queue.normal", true, false, false, args);
channel.queueBind("queue.normal", "exchange.normal", "");
//创建死信队列queue.dlx,并当到死信交换器exchange.dlx
channel.queueDeclare("queue.dlx", true, false, false, null);
channel.queueBind("queue.dlx", "exchange.dlx", "routingkey");
//向exchange.normal发布一条消息
channel.basicPublish("exchange.normal", "rk", MessageProperties.PERSISTENT_TEXT_PLAIN , "dlx".getBytes());
参考下图,生产者首先发送一条携带路由键为“rk”的消息,然后经过交换器 exchange.normal 顺利地存储到队列queue.normal 中。由于队列 queue.normal 设置了过期时间为 10s,在这 10s 内没有消费者消费这条消息,那么判定这条消息为过期。由于设置了 DLX,过期之时,消息被丢给交换器 exchange.dlx 中,这时找到与 exchange.dlx 匹配的队列 queue.dlx,最后消息被存储在 queue.dlx 这个死信队列中。对于 RabbitMQ 来说,DLX 是一个非常有用的特性。它可以处理异常情况下,消息不能够被消费者正确消费(消费者调用了 Basic.Nack 或者 Basic.Reject)而被置入死信队列中的情况,后续分析程序可以通过消费这个死信队列中的内容来分析当时所遇到的异常情况,进而可以改善和优化系统。
在上图中,不仅展示的是死信队列的用法,也是延迟队列的用法,对于 queue.dlx 这个死信队列来说,同样可以看
作延迟队列。假设一个应用中需要将每条消息都设置为 10 秒的延迟,生产者通过 exchange.normal 这个交换器将发送的消息存储在 queue.normal 这个队列中。消费者订阅的并非是 queue.normal 这个队列,而是 queue.dlx 这个队列。当消息从 queue.normal 这个队列中过期之后被存入 queue.dlx 这个队列中,消费者就恰巧消费到了延迟 10 秒的这条消息。
在真实应用中,对于延迟队列可以根据延迟时间的长短分为多个等级,一般分为 5 秒、10 秒、30 秒、1 分钟、5 分
钟、10 分钟、30 分钟、1 小时这几个维度,当然也可以再细化一下。
参考下图,为了简化说明,这里只设置了 5 秒、10 秒、30 秒、1 分钟这四个等级。根据应用需求的不同,生产者在发送消息的时候通过设置不同的路由键,以此将消息发送到与交换器绑定的不同的队列中。这里队列分别设置了过期时间为 5 秒、10 秒、30 秒、1 分钟,同时也分别配置了 DLX 和相应的死信队列。当相应的消息过期时,就会转存到相应的死信队列(即延迟队列)中,这样消费者根据业务自身的情况,分别选择不同延迟等级的延迟队列进行消费。
RabbitMQ 延迟消息插件
从安装插件开始,但首先,让我们看一下以下先决条件:
- RabbitMQ 版本 3.5.8 及更高版本。
- Erlang/OTP 18.0 及更高版本
插件安装
在Github下载插件。将插件复制到 RabbitMQ 的插件文件夹,然后运行以下命令启用它:
# 下载插件
wget https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases/download/3.11.1/rabbitmq_delayed_message_exchange-3.11.1.ez
# 将插件移动到plugins目录下
mv rabbitmq_delayed_message_exchange-3.11.1.ez ./rabbitmq_server-3.11.1/plugins/
# 启用插件
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
延迟消息交换器
要使用延迟消息交换器,只需声明一个类型为 x-delayed-message 的交换器,如下所示:
// ... elided code ...
Map<String, Object> args = new HashMap<String, Object>();
args.put("x-delayed-type", "direct");
channel.exchangeDeclare("my-exchange", "x-delayed-message", true, false, args);
// ... more code ...
稍后,我们将解释交换声明中的特殊参数 x-delayed-type 的含义。

延迟消息
要延迟消息,用户必须使用 x-delay 标头发布它,该标头接受一个整数,表示消息应由 RabbitMQ 延迟的毫秒数。值得注意的是,在此上下文中的延迟表示着消息路由到队列或其他交换器的延迟。交换器没有消费者的概念。
因此,一旦延迟过去,插件将尝试将消息路由到与交换器的路由规则匹配的队列。如果消息无法路由到任何队列,它将被丢弃。
// ... elided code ...
byte[] messageBodyBytes = "delayed payload".getBytes();
AMQP.BasicProperties.Builder props = new AMQP.BasicProperties.Builder();
headers = new HashMap<String, Object>();
headers.put("x-delay", 5000);
props.headers(headers);
channel.basicPublish("my-exchange", "", props.build(), messageBodyBytes);
在上面的示例中,消息在被插件路由之前将延迟五秒钟。
路由灵活性
当我们在上面声明交换时,我们使用了一个设置为 direct 的 x-delayed-type 参数。这告诉交换器我们希望它在路由消息、创建绑定等时具有什么样的行为。
检查延迟消息
一旦我们在消费者端收到消息,我们如何判断消息是否被延迟? x-delay 消息头由插件保留。如果您以 5000 毫秒的延迟发送消息,消费者会发现 x-delay 标头设置为 5000。
参考资料:
RabbitMQ 延迟消息实战的更多相关文章
- RabbitMQ延迟消息学习
准备做一个禁言自动解除的功能,立马想到了订单的超时自动解除,刚好最近在看RabbitMQ的实现,于是想用它实现,查询了相关文档发现确实可以实现,动手编写了这篇短文. 准备工作 1.Erlang安装请参 ...
- Spring Boot RabbitMQ 延迟消息实现完整版
概述 曾经去网易面试的时候,面试官问了我一个问题,说 下完订单后,如果用户未支付,需要取消订单,可以怎么做 我当时的回答是,用定时任务扫描DB表即可.面试官不是很满意,提出: 用定时任务无法做到准实时 ...
- RabbitMQ延迟消息的延迟极限是多少?
之前在写Spring Cloud Stream专题内容的时候,特地介绍了一下如何使用RabbitMQ的延迟消息来实现定时任务.最近正好因为开发碰到了使用过程中发现,延迟消息没有效果,消息直接就被消费了 ...
- RabbitMQ延迟消息:死信队列 | 延迟插件 | 二合一用法+踩坑手记+最佳使用心得
前言 前段时间写过一篇: # RabbitMQ:消息丢失 | 消息重复 | 消息积压的原因+解决方案+网上学不到的使用心得 很多人加了我好友,说很喜欢这篇文章,也问了我一些问题. 因为最近工作比较忙, ...
- RabbitMQ延迟消息队列实现定时任务完整代码示例
- Spring Cloud Stream 使用延迟消息实现定时任务(RabbitMQ)
应用场景 通常在应用开发中我们会碰到定时任务的需求,比如未付款订单,超过一定时间后,系统自动取消订单并释放占有物品. 许多同学的第一反应就是通过spring的schedule定时任务轮询数据库来实现, ...
- 15-EasyNetQ之对延迟消息插件的支持
RabbitMQ延迟消息插件仍然在实验阶段.你使用这个功能要自担风险. RabbitMQ延迟消息插件为RabbitMQ增加了新的交换机类型,允许延时消息投递. EasyNetQ为交换机通过定义一种新的 ...
- EasyNetQ使用(八)【对延迟消息插件的支持,自动订阅者】
RabbitMQ延迟消息插件仍然在实验阶段.你使用这个功能要自担风险. RabbitMQ延迟消息插件为RabbitMQ增加了新的交换机类型,允许延时消息投递. EasyNetQ为交换机通过定义一种新的 ...
- C# RabbitMQ延迟队列功能实战项目演练
一.需求背景 当用户在商城上进行下单支付,我们假设如果8小时没有进行支付,那么就后台自动对该笔交易的状态修改为订单关闭取消,同时给用户发送一份邮件提醒.那么我们应用程序如何实现这样的需求场景呢?在之前 ...
- SpringBoot | 第三十八章:基于RabbitMQ实现消息延迟队列方案
前言 前段时间在编写通用的消息通知服务时,由于需要实现类似通知失败时,需要延后几分钟再次进行发送,进行多次尝试后,进入定时发送机制.此机制,在原先对接银联支付时,银联的异步通知也是类似的,在第一次通知 ...
随机推荐
- python3实现AES加密
前言 这几天研究了一下 python 实现 AES 加密,有很多坑 AES 加密的参数及其条件 这个 AES 加密的主要坑就在于这些条件, 首先 aes 加密有一下几个参数 秘钥:加密的时候用秘钥,解 ...
- SSH(五)spring整合hibernate
一.创建hibernate实体映射文件. 在实体所在包创建映射文件product.hbm.xml,引入hibernate的映射约束.(该约束位于hibernate3.jar里面hibernate-ma ...
- elasticsearch 聚合之 date_histogram 聚合
目录 1.背景 2.bucket_key如何计算 3.前置知识 4.日历和固定时间间隔 4.1 Calendar intervals 日历间隔 4.2 Fixed intervals 固定间隔 5.数 ...
- 【机器学习】李宏毅——Explainable ML(可解释性的机器学习)
在前面的学习之中,我们已经学习了很多的模型,它能够针对特定的任务,接受我们的输入并产生目标的输出.但我们并不满足于此,我们甚至希望机器告诉我们,它是如何得到这个答案的,而这就是可解释的机器学习. Wh ...
- MAUI新生4.6-主题设置LightTheme&DarkTheme
通过主题设置,可以在运行时更改应用的主题外观,比如切换亮色主题和暗黑主题.主题设置并没有新的知识点,基本的原理如下: 定义每个应用主题的ResourceDictionary,每个ResourceDic ...
- 判断条件为NULL
在ASCII码表里NULL的二进制位0.所以NULL作为判断条件时,表示为假的意思. ASCII表 二进制 字符 ...
- 从工具到实践:如何在GitHub上保障开源项目安全?
1998年,Christine Peterson创造了 "开源软件"这个词.她解释道:"这是刻意为之,为了让其他人更容易理解这个领域".同年,O'Reilly组 ...
- 【架构设计】保持简单轻量设计的三个原则——DRY,KISS, YAGNI
前言 一个软件轻量简单的软件架构是非常重要的,它可以让我们花最小的代价就能满足业务上的需求.那如何保证轻量简单呢?那今天就和大家分享下这其中的秘密,也就是3个重要的指导原则,KISS原则,YAGNI原 ...
- Shiro-721反序列化漏洞
漏洞名称 Shiro-721(Apache Shiro Padding Oracle Attack)反序列化 利用条件 Apache Shiro < 1.4.2 漏洞原理 Apache Shir ...
- 大数据分析新玩法之Kusto宝典 - 新书发布,免费发行
我很高兴地跟大家分享,我在元旦期间编写的一本新书今天上线,并且免费发行,大家可以随时通过 https://kusto.book.xizhang.com 这个地址访问,也可以下载 PDF 的版本, 这本 ...