Rabbit MQ——死信队列实现延迟队列

2,342 阅读3分钟

MQ死信队列实现延迟队列

概念

死信:指无法被消费的消息,由于特定的原因消息无法被消费,就没有后续的处理了,就变成了死信,也就有了死信队列。

场景

所有需要进行延迟操作的业务,如:

  • 订单30分钟后接单、未支付取消订单
  • 消息延迟提醒
  • 数据定时同步

好处

  • 相比轮询:消息时效性高
  • 对于DB来讲:频繁的扫描数据表效率较低,消耗资源大

RabbitMQ 死信队列

死信来源:

  1. 消息被Consumer拒绝,并且reject为false,不会被再次放到队列里,被其他消费者消费
  2. 消息超时 TTL
  3. 队列达到最大长度 x-max-length

死信处理方式:

一般都是监听死信队列,进行相应的业务处理,如下单30分钟未支付就取消该订单。

实现方式——消息过期:

消息过期转入死信队列,实现延迟队列

RabbitMQ本身不具备延迟消息功能,可以通过TTL、DLX特性实现, 参数x-dead-letter-exchangex-message-ttl实现死信队列

x-message-ttl

Time To Live消息的存活时间,单位毫秒,TTL过期了消息进入死信队列

x-dead-letter-exchange

设置消息过期转向到的路由

整体流程
  1. 新建一个exchange作为死信交换器Dead Letter Exchange
  2. 新建一个队列作为死信处理队列 上图中的dlx.queue
  3. 绑定死信处理队列和死信交换器
  4. 将正常的业务队列/需要延迟处理的队列添加x-message-ttlx-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);  

比如秒杀业务,此时在死信队列中的就是秒杀失败。