MQ死信队列实现延迟队列
概念
死信:指无法被消费的消息,由于特定的原因消息无法被消费,就没有后续的处理了,就变成了死信,也就有了死信队列。
场景
所有需要进行延迟操作的业务,如:
- 订单30分钟后接单、未支付取消订单
- 消息延迟提醒
- 数据定时同步
好处
- 相比轮询:消息时效性高
- 对于DB来讲:频繁的扫描数据表效率较低,消耗资源大
RabbitMQ 死信队列
死信来源:
- 消息被
Consumer拒绝,并且reject为false,不会被再次放到队列里,被其他消费者消费 - 消息超时
TTL - 队列达到最大长度
x-max-length
死信处理方式:
一般都是监听死信队列,进行相应的业务处理,如下单30分钟未支付就取消该订单。
实现方式——消息过期:
消息过期转入死信队列,实现延迟队列
RabbitMQ本身不具备延迟消息功能,可以通过TTL、DLX特性实现,
参数x-dead-letter-exchange和x-message-ttl实现死信队列
x-message-ttl
Time To Live消息的存活时间,单位毫秒,TTL过期了消息进入死信队列
x-dead-letter-exchange
设置消息过期转向到的路由

整体流程
- 新建一个
exchange作为死信交换器Dead Letter Exchange - 新建一个队列作为死信处理队列
上图中的dlx.queue - 绑定死信处理队列和死信交换器
- 将正常的业务队列/需要延迟处理的队列添加
x-message-ttl和x-dead-letter-exchange参数,这样当触发死信的条件后,就会转发到死信交换器,就由监听dlx.queue死信队列的程序进行相应业务处理了
效果
订单下单后10s内支付:

Producer
#region 死信队列实现延迟队列
Console.WriteLine("【生产者】下单后需要10秒内进行支付");
var arguments = new Dictionary<string, object>();
arguments.Add("x-dead-letter-exchange", dlxExchangeName); //过期消息转向路由
arguments.Add("x-message-ttl", 10000);
//arguments.Add("x-dead-letter-routing-key", ""); //过期消息转向路由匹配的routingkey
//创建死信交换器和队列
channel.ExchangeDeclare("dlx.exchange", "topic", true, false, null);
channel.QueueDeclare("dlx.queue", durable: true, exclusive: false, autoDelete: false, null);
//正常的队列绑定
channel.ExchangeDeclare(orderExchangeName, "topic", true, false, null);
channel.QueueDeclare(orderNormalQueueName, true, false, false, arguments); //正常队列绑定死信参数条件,如果达不到要求就进入死信了
channel.QueueBind(orderNormalQueueName, orderExchangeName, "");
//创建死信交换器和队列
channel.ExchangeDeclare(dlxExchangeName, "topic", true, false, null);
channel.QueueDeclare(dlxQueueName, true, false, false, null);
channel.QueueBind(dlxQueueName, dlxExchangeName, "");
var properties = channel.CreateBasicProperties();
properties.Persistent = true;
for (var i = 0; i < 10; i++)
{
string message = $"订单:【{(i + 1).ToString("000000")}】需要10秒内支付:订单时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}";
channel.BasicPublish(orderExchangeName, "", basicProperties: properties, body: Encoding.UTF8.GetBytes(message));
Console.WriteLine($"消息已发送:{message}");
Thread.Sleep(5000);
}
Console.ReadKey();
#endregion
Consumer
#region 消费死信队列
Console.WriteLine("【消费者】死信队列,检测下单10秒内未支付的订单");
//订阅死信队列进行处理
channel.QueueDeclare("dlx.queue", durable: true, exclusive: false, autoDelete: false);
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, arg) =>
{
var body = arg.Body;
var message = Encoding.UTF8.GetString(body);
Console.WriteLine($"读取队列数据:{Environment.NewLine}{message}{Environment.NewLine}已取消该订单状态,取消时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}{Environment.NewLine}");
channel.BasicAck(arg.DeliveryTag, false); //手动发送ACK应答
};
channel.BasicConsume("dlx.queue", false, consumer);
Console.ReadKey();
#endregion

实现方式——队列达到最大长度:
比如队列限制最大长度为50,超出50条的消息就被路由到死信队列中,一般可用于秒杀/限流服务
arguments.Add("x-max-length", 50);
比如秒杀业务,此时在死信队列中的就是秒杀失败。