NetCore RabbitMQ 高级特性 消息存活周期TTL、死信交换机/死信对列DLX,延迟队列,及幂等性的保障
十年河东,十年河西,莫欺少年穷
学无止境,精益求精
上一节介绍了RabbitMQ定向模式,本篇介绍Rabbitmq 的消息确认机制
我的系列博客:
NetCore RabbitMQ高级特性 持久化 及 消息优先级
NetCore RabbitMQ 简介及兔子生产者、消费者 【简单模式,work工作模式,竞争消费】
和上篇文章一致,先看个表格,该表格展示了队列的高级特性
x-expires | 队列的存活时间 | Number[毫秒] |
x-message-ttl | 消息的存活时间 | Number[毫秒] |
x-single-active-consumer | 表示队列是否是单一消费者 | Bool |
x-max-length | 队列可容纳的消息的最大条数 | Number【字节】 |
x-max-length-bytes | 队列可容纳的消息的最大字节数 | Number |
x-max-priority | 队列的优先级 | Number |
x-overflow | 队列中的消息溢出时,如何处理这些消息.要么丢弃队列头部的消息,要么拒绝接收后面生产者发送过来的所有消息. | String |
x-dead-letter-exchange | 溢出的消息需要发送到绑定该死信交换机的队列 | String |
x-dead-letter-routing-key | 溢出的消息需要发送到绑定该死信交换机,并且路由键匹配的队列 | String |
x-queue-mode | 默认懒人模式 lazy | String |
x-queue-version | 版本 | Number |
x-queue-master-locator | 集群相关设置,Master接点 |
上篇博客介绍了高级特性持久化(durable ) 和 优先级(x-max-priority),本篇博客介绍队列/消息存活时间(x-message-ttl) 和 死信队列(x-dead-letter-exchange)
TTL 特性很好理解,是指队列中消息的存活周期,你可以设置队列中消息的存活周期为5分钟,5分钟周期内没有消费者进行消费,消息自动过期,被删除。
TTL是针对队列内的消息,到了设定的时间周期后,消息会被删除掉,队列依旧存在,如果使用(x-expirse)设定周期,那么到达时间后,队列也会被一起删除。
dead-letter-exchange 被称之为死信队列,何为死信队列?
死信队列DLX
是指当消息变成 dead message 后,可以被重新发送到另外一个交换机,这个交换机就是DLX死信队列(死信交换机)
消息在什么情况下变成Dead Message 呢?
1、队列消息长度达到最大限制
2、消费者拒绝接收消息,basicNack,basicReject,并且不把消息放入原目标队列,requeue=false
3、原队列存在消息存活时间,当到达存活时间后未被消费,消息变成dead message
队列如何绑定死信交换机呢?
队列绑定死信交换机时,需要设置两个参数,x-dead-letter-exchange 、x-dead-letter-routing-key
x-dead-letter-exchange 对应的为死信交换机的名称
x-dead-letter-routing-key 对应的为死信交换机绑定队列的routingKey
死信交换机和普通的交换机没什么区别,只是叫法不同而已,也需要通过routingKey绑定队列
开启代码模式

using RabbitMQ.Client; using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace RabbitMqProducer { class Program { static void Main(string[] args) { ConnectionFactory factory = new ConnectionFactory(); factory.HostName = "127.0.0.1"; //主机名 factory.UserName = "guest";//使用的用户 factory.Password = "guest";//用户密码 factory.Port = 5672;//端口号 factory.VirtualHost = "/"; //虚拟主机 factory.MaxMessageSize = 1024; //消息最大字节数 using (var connection = factory.CreateConnection()) { //rabbitMQ 基于信道进行通信,因此,我们需要实例化信道Channel using (var channel = connection.CreateModel()) { //声明正常的交换机 string Ename = "MyExChange"; //声明死信交换机 string EnameDLX = "MyExChange_DLX"; //durable 是否持久化 //void ExchangeDeclare(string exchange, string type, bool durable, bool autoDelete, IDictionary<string, object> arguments); channel.ExchangeDeclare(Ename, ExchangeType.Direct, true, false, null); channel.ExchangeDeclare(EnameDLX, ExchangeType.Direct, true, false, null); //声明正常的队列 string QnameName = "MyQueue"; //声明死信队列 string QnameNameDLX = "MyQueue_DLX"; string routingKey = "MyroutingKey"; // 正常队列的routingKey string routingKeyDLX = "MyroutingKey_DLX"; // 死信队列的routingKey Dictionary<string, object> arguments = new Dictionary<string, object>(); ////队列优先级最高为10,不加x-max-priority的话,计算发布时设置了消息的优先级也不会生效 arguments.Add("x-max-priority", 10); arguments.Add("x-message-ttl", 1000 * 10);//10秒消息过期 arguments.Add("x-max-length", 100);//队列最大长度为100,超出这个长度后接收的消息为dead message arguments.Add("x-dead-letter-exchange", EnameDLX);// arguments.Add("x-dead-letter-routing-key", routingKeyDLX);// channel.QueueDeclare(QnameName, true, false, false, arguments); channel.QueueDeclare(QnameNameDLX, true, false, false, null);//死信队列不需要设置其他属性 因此arguments为NULL //正常队列和正常交换机绑定 channel.QueueBind(QnameName, Ename, routingKey); //死信队列和死信交换机绑定 channel.QueueBind(QnameNameDLX, EnameDLX, routingKeyDLX); //正常队列和死信交换机绑定 channel.QueueBind(QnameName, EnameDLX, routingKeyDLX); var messages = "MyHello,RabbitMQ"; // var properties = channel.CreateBasicProperties(); properties.Priority = 9;//消息的优先级 值越大 优先级越高 0~9 注意,必须要开启队列的优先级,否则此处消息优先级的声明无效 properties.ContentType = "text/plain";//消息的内输出格式 for (int i = 0; i < 10; i++) { //此处测试过期时的死信队列 channel.BasicPublish(Ename, routingKey, properties, Encoding.UTF8.GetBytes(messages)); //发送消息 } } } Console.Read(); } } }
上述代码过程如下:
1、创建正常交换机 及 死信交换机
2、创建正常队列,并为正常队列声明相关属性
//声明正常的队列 string QnameName = "MyQueue"; //声明死信队列 string QnameNameDLX = "MyQueue_DLX"; string routingKey = "MyroutingKey"; // 正常队列的routingKey string routingKeyDLX = "MyroutingKey_DLX"; // 死信队列的routingKey Dictionary<string, object> arguments = new Dictionary<string, object>(); ////队列优先级最高为10,不加x-max-priority的话,计算发布时设置了消息的优先级也不会生效 arguments.Add("x-max-priority", 10); arguments.Add("x-message-ttl", 1000 * 10);//10秒消息过期 arguments.Add("x-max-length", 100);//队列最大长度为100,超出这个长度后接收的消息为dead message arguments.Add("x-dead-letter-exchange", EnameDLX);// arguments.Add("x-dead-letter-routing-key", routingKeyDLX);// channel.QueueDeclare(QnameName, true, false, false, arguments);
3、创建死信队列
channel.QueueDeclare(QnameNameDLX, true, false, false, null);//死信队列不需要设置其他属性 因此arguments为NULL
4、正常队列与正常队列交换机/路由相互绑定,死信队列与死信队列交换机/路由相互绑定,正常队列与死信交换机/路由相互绑定【重点,缺一不可】
//正常队列和正常交换机绑定 channel.QueueBind(QnameName, Ename, routingKey); //死信队列和死信交换机绑定 channel.QueueBind(QnameNameDLX, EnameDLX, routingKeyDLX); //正常队列和死信交换机绑定 channel.QueueBind(QnameName, EnameDLX, routingKeyDLX);
5、消息发送
for (int i = 0; i < 10; i++) { //此处测试过期时的死信队列 channel.BasicPublish(Ename, routingKey, properties, Encoding.UTF8.GetBytes(messages)); //发送消息 }
等待10秒,等消息过期转变为dead message 后,观察消息能否转到死信队列中。
10 、 9 、8 、、、、
延迟队列
延迟队列是指:消息进入队列后,不能被立即消费,达到指定的时间后,方可被消费。
使用场景如下:
例如下单后,30分钟不支付,订单取消。
新用户注册会员后,7天后进行短信问候等
但是,RabbitMQ中并没有延迟队列的概念,那么我们怎么实现延迟队列呢?
实现延迟队列可采用:TTL+DLX 也就是消息生存周期+死信队列。
上述的生产者代码已经实现了延迟队列,我们只需让消费者侦听死信队列即可。
@ 侦听死信队列,模拟延迟效果。
using RabbitMQ.Client; using RabbitMQ.Client.Events; using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace RabbitMQConsumer_2 { class Program { static void Main(string[] args) { ConnectionFactory factory = new ConnectionFactory(); factory.HostName = "127.0.0.1"; //主机名 factory.UserName = "guest";//使用的用户 factory.Password = "guest";//用户密码 factory.Port = 5672;//端口号 factory.VirtualHost = "/"; //虚拟主机 factory.MaxMessageSize = 1024; //消息最大字节数 //创建连接 var connection = factory.CreateConnection(); //创建通道 var channel = connection.CreateModel(); //事件基本消费者 EventingBasicConsumer consumer = new EventingBasicConsumer(channel); //接收到消息事件 consumer.Received += (ch, ea) => { var message = Encoding.UTF8.GetString(ea.Body.ToArray()); Console.WriteLine($"消费者收到消息: {message}"); channel.BasicAck(ea.DeliveryTag, false); }; //启动消费者 string Qname = "MyQueue_DLX"; channel.BasicConsume(Qname, true, consumer);//开启自动确认 Console.WriteLine("消费者已启动"); Console.ReadKey(); channel.Dispose(); connection.Close(); } } }
幂等性
何为幂等性?
在MQ中,消费多条相同的消息和消费一条该消息得到相同的结果。
实际场景中,例如转账,支付送积分等,都需要保障幂等性。
小明给大牛转了500块钱,由于网络问题,消息消费失败,重发了该消息,那么我们要扣大牛两次金额吗?这显然是不行的,因此,某些场景中,幂等性格外重要。
网上有很多保证幂等性的方案,例如通过乐观锁版本号来控制等,总之,自行必应吧,网上有很多方案。
@天才卧龙的博客
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下