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实现消息延迟队列方案
前言 前段时间在编写通用的消息通知服务时,由于需要实现类似通知失败时,需要延后几分钟再次进行发送,进行多次尝试后,进入定时发送机制.此机制,在原先对接银联支付时,银联的异步通知也是类似的,在第一次通知 ...
随机推荐
- adb shell 全局查找文件
借助busybox 由于安卓手机没有 find 命令,所以我们需要借助busybox中的find命令 busybox 下载地址 这里我下载的是 busybox-armv6l, 一般这个版本就可以,下载 ...
- ArcEngine 序列化AO对象
ArcEngine中只要是继承了IPersistStream接口的对象均可调用ArcEngine中的类库进行序列化和反序列化.一般我们会序列化成xml格式,作为字符串存储,需要的时候,反序列化为对象. ...
- MySQL库,表,数据的操作
数据库的操作 1. 创建数据库 create database [if not exists] `数据库名` charset=字符编码(utf8mb4); 如果多次创建会报错 如果不指定字符编码,默认 ...
- 通过启动脚本控制PHP-FPM开关
vi /etc/init.d/php-fpm 复制粘贴以下内容: #! /bin/sh# Comments to support chkconfig on CentOS# chkconfig: 234 ...
- PHP8.1.10手动安装教程及报错解决梳理
安装教程参考一:https://www.cnblogs.com/haw2106/p/9839655.html 安装教程参考二:https://www.cnblogs.com/jiangfeilong/ ...
- 源码解读之TypeScript类型覆盖检测工具type-coverage
因为团队内部开启了一个持续的前端代码质量改进计划,其中一个专项就是TS类型覆盖率,期间用到了type-coverage这个仓库,所以借这篇文章分享一下这个工具,并顺便从源码阅读的角度来分析一下该工具的 ...
- 知识分享-消息中间件详解+rabbitMQ
知识分享-消息中间件详解+rabbitMQ 消息中间件 概述 消息中间件是基于队列与消息传递技术,在网络环境中为应用系统提供同步或异步.可靠的消息传输的支撑性软件系统. 应用场景 异步处理 对于电商a ...
- Flutter异常监控 - 贰 | 框架Catcher原理分析
前言 在给 Flutter 应用做异常监控的时候,一开始我是拒绝滴,如果不考虑 Flutter Engine 和 native 侧的监控,用我另一篇文章中不得不知道的 Flutter 异常捕获知识点 ...
- [R语言] ggplot2入门笔记3—通用教程如何自定义ggplot2
通用教程简介(Introduction To ggplot2) 代码下载地址 以前,我们看到了使用ggplot2软件包制作图表的简短教程.它很快涉及制作ggplot的各个方面.现在,这是一个完整而完整 ...
- A排列方案
递归实现排列型枚举 把 1∼n 这 n 个整数排成一行后随机打乱顺序,输出所有可能的次序. 输入格式 一个整数 n. 输出格式 按照从小到大的顺序输出所有方案,每行 1 个. 首先,同一行相邻两个数用 ...