概念
死信,顧名思義就是無法被消費的消息,字面意思可以這樣理解,一般來說,producer 將消息投遞到 broker 或者直接到queue 裏了,consumer 從 queue 取出消息進行消費,但某些時候由於特定的原因導致 queue 中的某些消息無法被消費,這樣的消息如果沒有後續的處理,就變成了死信,有死信自然就有了死信隊列。
應用場景
爲了保證訂單業務的消息數據不丟失,需要使用到 RabbitMQ 的死信隊列機制,當消息 消費發生異常時,將消息投入死信隊列中.還有比如說: 用戶在商城下單成功並點擊去支付後在指定時 間未支付時自動失效
死信的來源
- 消息TTL過期
- 隊列達到最大長度(隊列滿了,無法添加數據到mq中)
- 消息被拒絕(basic.reject或basic.nack)並且requeue = false
消息TTL過期
生產者代碼
消費者代碼
Consumer1代碼
using rabbitmq.common;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System.Text;
namespace Exchange.Consumer1
{
public class Program
{
static void Main(string[] args)
{
Console.WriteLine("C1 開始接消息");
using var channel = RabbitmqUntils.GetChannel();
/*
申明一個 test_exchange 交換機
配置轉發到死信隊列參數
綁定交換機與對列
*/
channel.ExchangeDeclare(RabbitmqUntils.test_exchange, "direct",false,false,null);
var arguments = new Dictionary<string, object> { };
arguments.Add("x-dead-letter-exchange", "dead_exchange");//正常隊列設置死信交換機 參數 key 是固定值
arguments.Add("x-dead-letter-routing-key", "dead");//正常隊列設置死信 routing-key 參數 key 是固定值
channel.QueueDeclare(RabbitmqUntils.test_queue, false,false,false, arguments);
channel.QueueBind(RabbitmqUntils.test_queue, RabbitmqUntils.test_exchange, RabbitmqUntils.test_routingkey, null);
/*
申明一個死信交換機
綁定交換機與對列
*/
channel.ExchangeDeclare(RabbitmqUntils.dead_exchange, "direct", false, false, null);
channel.QueueDeclare("dead_queue", false, false, false, null);
channel.QueueBind(RabbitmqUntils.dead_queue, RabbitmqUntils.dead_exchange, RabbitmqUntils.dead_routingkey, null);
//事件對象
var consumer = new EventingBasicConsumer(channel);
channel.BasicConsume(queue: RabbitmqUntils.test_queue, true, consumer);
consumer.Received += (sender, e) =>
{
var body = e.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
Console.WriteLine("C1 接收消息: {0}", message);
};
Console.ReadKey();
}
}
}
Consumer2代碼
using rabbitmq.common;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System.Text;
namespace Exchange.Consumer2
{
public class Program
{
static void Main(string[] args)
{
Console.WriteLine("C2 開始接消息");
using var channel = RabbitmqUntils.GetChannel();
/*
申明一個死信交換機
綁定交換機與對列
*/
channel.ExchangeDeclare(RabbitmqUntils.dead_exchange, "direct", false, false, null);
channel.QueueDeclare("dead_queue", false, false, false, null);
channel.QueueBind(RabbitmqUntils.dead_queue, RabbitmqUntils.dead_exchange, RabbitmqUntils.dead_routingkey, null);
//事件對象
var consumer = new EventingBasicConsumer(channel);
channel.BasicConsume(queue: "dead_queue", true, consumer);
consumer.Received += (sender, e) =>
{
var body = e.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
Console.WriteLine("C2 接收消息: {0}", message);
};
Console.ReadKey();
}
}
}
測試效果
- 先啓動C1 創建交換機,隊列和交換機與對列之間的綁定關係
- 此時關閉C1,模擬C1無法正常消費
- 啓動生產者發送10條數據, 可在
test_queue
先有10條數據,10s後test_queue
10條數據消息,已將10條數據轉發到dead_queue
中
- 最後啓動C2 消費死信隊列中的數據
隊列達到最大長度
生產者去掉設置ttl
C1 添加代碼
啓動C1之前先刪除隊列否則會出現一下錯誤:
測試效果
消息被拒絕
C1代碼調整
去掉設置長度限制代碼並刪除隊列重新啓動C1
調整爲手動應答 autoAck:false