6.【RabbitMQ实战】- 死信队列

概念

死信,顾名思义就是无法被消费的消息,字面意思可以这样理解,一般来说,producer 将消息投递到 broker 或者直接到queue 里了,consumer 从 queue 取出消息进行消费,但某些时候由于特定的原因导致 queue 中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信,有死信自然就有了死信队列。

应用场景

为了保证订单业务的消息数据不丢失,需要使用到 RabbitMQ 的死信队列机制,当消息 消费发生异常时,将消息投入死信队列中.还有比如说: 用户在商城下单成功并点击去支付后在指定时 间未支付时自动失效

死信的来源

  • 消息TTL过期
  • 队列达到最大长度(队列满了,无法添加数据到mq中)
  • 消息被拒绝(basic.reject或basic.nack)并且requeue = false

image.png

消息TTL过期

生产者代码

image.png

消费者代码

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 创建交换机,队列和交换机与对列之间的绑定关系

image.png

  • 此时关闭C1,模拟C1无法正常消费
  • 启动生产者发送10条数据, 可在test_queue先有10条数据,10s后test_queue10条数据消息,已将10条数据转发到dead_queue

image.png
image.png

  • 最后启动C2 消费死信队列中的数据

image.png

队列达到最大长度

生产者去掉设置ttl
image.png
C1 添加代码
image.png

启动C1之前先删除队列否则会出现一下错误:
image.png

测试效果

由于设置了队列做多6个消息所以剩下的4个消息被C2消费了
image.png
image.png

消息被拒绝

C1代码调整

去掉设置长度限制代码并删除队列重新启动C1
image.png
调整为手动应答 autoAck:false
image.png

模拟消息1被拒绝
image.png

测试效果

image.png
image.png
image.png
image.png

posted @ 2023-04-12 22:46  无敌土豆  阅读(40)  评论(0编辑  收藏  举报