6.【RabbitMQ实战】- 死信队列
概念
死信,顾名思义就是无法被消费的消息,字面意思可以这样理解,一般来说,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