• NetCore RabbitMQ 高级特性 消息存活周期TTL、死信交换机/死信对列DLX,延迟队列,及幂等性的保障


    十年河东,十年河西,莫欺少年穷

    学无止境,精益求精

    上一节介绍了RabbitMQ定向模式,本篇介绍Rabbitmq 的消息确认机制

    我的系列博客:

    NetCore RabbitMQ高级特性 持久化 及 消息优先级

    NetCore RabbitMQ 的消息确认机制 

    NetCore RabbitMQ Topics 通配符模式

    NetCore RabbitMQ ,Routing定向模式

    NetCore RabbitMQ 发布订阅模式,消息广播

    RabbitMQ的六种工作模式

    NetCore RabbitMQ 简介及兔子生产者、消费者 【简单模式,work工作模式,竞争消费】

    windows环境下,RabbitMQ 安装教程

    和上篇文章一致,先看个表格,该表格展示了队列的高级特性

    x-expires 队列的存活时间 Number[毫秒]
    x-message-ttl 消息的存活时间 Number[毫秒]
    x-single-active-consumer 表示队列是否是单一消费者 Bool
    x-max-length 队列可容纳的消息的最大条数 Number【字节】
    x-max-length-bytes 队列可容纳的消息的最大字节数 Number 
    x-max-priority 队列的优先级 Number 
    x-overflow 队列中的消息溢出时,如何处理这些消息.要么丢弃队列头部的消息,要么拒绝接收后面生产者发送过来的所有消息. String
    x-dead-letter-exchange 溢出的消息需要发送到绑定该死信交换机的队列 String
    x-dead-letter-routing-key 溢出的消息需要发送到绑定该死信交换机,并且路由键匹配的队列 String
    x-queue-mode 默认懒人模式  lazy String
    x-queue-version 版本 Number
    x-queue-master-locator 集群相关设置,Master接点

    上篇博客介绍了高级特性持久化(durable ) 和 优先级(x-max-priority),本篇博客介绍队列/消息存活时间(x-message-ttl) 和 死信队列(x-dead-letter-exchange) 

    TTL 特性很好理解,是指队列中消息的存活周期,你可以设置队列中消息的存活周期为5分钟,5分钟周期内没有消费者进行消费,消息自动过期,被删除。

    TTL是针对队列内的消息,到了设定的时间周期后,消息会被删除掉,队列依旧存在,如果使用(x-expirse)设定周期,那么到达时间后,队列也会被一起删除。

    dead-letter-exchange 被称之为死信队列,何为死信队列?

    死信队列DLX

    是指当消息变成 dead message 后,可以被重新发送到另外一个交换机,这个交换机就是DLX死信队列(死信交换机)

    消息在什么情况下变成Dead Message 呢?

    1、队列消息长度达到最大限制

    2、消费者拒绝接收消息,basicNack,basicReject,并且不把消息放入原目标队列,requeue=false

    3、原队列存在消息存活时间,当到达存活时间后未被消费,消息变成dead message

    队列如何绑定死信交换机呢?

    队列绑定死信交换机时,需要设置两个参数,x-dead-letter-exchange 、x-dead-letter-routing-key

    x-dead-letter-exchange 对应的为死信交换机的名称

    x-dead-letter-routing-key 对应的为死信交换机绑定队列的routingKey

    死信交换机和普通的交换机没什么区别,只是叫法不同而已,也需要通过routingKey绑定队列

     开启代码模式

    using RabbitMQ.Client;
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Threading;
    
    namespace RabbitMqProducer
    {
        class Program
        {
            static void Main(string[] args)
            {
                ConnectionFactory factory = new ConnectionFactory();
                factory.HostName = "127.0.0.1"; //主机名
                factory.UserName = "guest";//使用的用户
                factory.Password = "guest";//用户密码
                factory.Port = 5672;//端口号
                factory.VirtualHost = "/"; //虚拟主机
                factory.MaxMessageSize = 1024; //消息最大字节数
                using (var connection = factory.CreateConnection())
                {
                    //rabbitMQ 基于信道进行通信,因此,我们需要实例化信道Channel
                    using (var channel = connection.CreateModel())
                    {
                        //声明正常的交换机
                        string Ename = "MyExChange";
                        //声明死信交换机
                        string EnameDLX = "MyExChange_DLX";
                        //durable 是否持久化
                        //void ExchangeDeclare(string exchange, string type, bool durable, bool autoDelete, IDictionary<string, object> arguments);
                        channel.ExchangeDeclare(Ename, ExchangeType.Direct, true, false, null);
                        channel.ExchangeDeclare(EnameDLX, ExchangeType.Direct, true, false, null);
                        //声明正常的队列 
                        string QnameName = "MyQueue";
                        //声明死信队列 
                        string QnameNameDLX = "MyQueue_DLX";
                        string routingKey = "MyroutingKey"; // 正常队列的routingKey
                        string routingKeyDLX = "MyroutingKey_DLX"; // 死信队列的routingKey
                        Dictionary<string, object> arguments = new Dictionary<string, object>();
                        ////队列优先级最高为10,不加x-max-priority的话,计算发布时设置了消息的优先级也不会生效
                        arguments.Add("x-max-priority", 10);
                        arguments.Add("x-message-ttl", 1000 * 10);//10秒消息过期
                        arguments.Add("x-max-length", 100);//队列最大长度为100,超出这个长度后接收的消息为dead message
                        arguments.Add("x-dead-letter-exchange", EnameDLX);//
                        arguments.Add("x-dead-letter-routing-key", routingKeyDLX);//
                        channel.QueueDeclare(QnameName, true, false, false, arguments); 
                        channel.QueueDeclare(QnameNameDLX, true, false, false, null);//死信队列不需要设置其他属性 因此arguments为NULL
    
                      
                        //正常队列和正常交换机绑定                                          
                        channel.QueueBind(QnameName, Ename, routingKey);
                        //死信队列和死信交换机绑定
                        channel.QueueBind(QnameNameDLX, EnameDLX, routingKeyDLX); 
                        //正常队列和死信交换机绑定
                        channel.QueueBind(QnameName, EnameDLX, routingKeyDLX);
    
                        var messages = "MyHello,RabbitMQ"; //   
                        var properties = channel.CreateBasicProperties();
                        properties.Priority = 9;//消息的优先级  值越大 优先级越高 0~9  注意,必须要开启队列的优先级,否则此处消息优先级的声明无效
                        properties.ContentType = "text/plain";//消息的内输出格式 
                        for (int i = 0; i < 10; i++)
                        {
                            //此处测试过期时的死信队列
                            channel.BasicPublish(Ename, routingKey, properties, Encoding.UTF8.GetBytes(messages));  //发送消息 
                        } 
                    }
                }
                Console.Read();
            }
        }
    }
    View Code

    上述代码过程如下:

    1、创建正常交换机 及 死信交换机

    2、创建正常队列,并为正常队列声明相关属性

      //声明正常的队列 
                        string QnameName = "MyQueue";
                        //声明死信队列 
                        string QnameNameDLX = "MyQueue_DLX";
                        string routingKey = "MyroutingKey"; // 正常队列的routingKey
                        string routingKeyDLX = "MyroutingKey_DLX"; // 死信队列的routingKey
                        Dictionary<string, object> arguments = new Dictionary<string, object>();
                        ////队列优先级最高为10,不加x-max-priority的话,计算发布时设置了消息的优先级也不会生效
                        arguments.Add("x-max-priority", 10);
                        arguments.Add("x-message-ttl", 1000 * 10);//10秒消息过期
                        arguments.Add("x-max-length", 100);//队列最大长度为100,超出这个长度后接收的消息为dead message
                        arguments.Add("x-dead-letter-exchange", EnameDLX);//
                        arguments.Add("x-dead-letter-routing-key", routingKeyDLX);//
                        channel.QueueDeclare(QnameName, true, false, false, arguments); 

    3、创建死信队列 

                        channel.QueueDeclare(QnameNameDLX, true, false, false, null);//死信队列不需要设置其他属性 因此arguments为NULL

    4、正常队列与正常队列交换机/路由相互绑定,死信队列与死信队列交换机/路由相互绑定,正常队列与死信交换机/路由相互绑定【重点,缺一不可】

                        //正常队列和正常交换机绑定                                          
                        channel.QueueBind(QnameName, Ename, routingKey);
                        //死信队列和死信交换机绑定
                        channel.QueueBind(QnameNameDLX, EnameDLX, routingKeyDLX); 
                        //正常队列和死信交换机绑定
                        channel.QueueBind(QnameName, EnameDLX, routingKeyDLX);

    5、消息发送

                        for (int i = 0; i < 10; i++)
                        {
                            //此处测试过期时的死信队列
                            channel.BasicPublish(Ename, routingKey, properties, Encoding.UTF8.GetBytes(messages));  //发送消息 
                        } 

     等待10秒,等消息过期转变为dead message 后,观察消息能否转到死信队列中。

    10 、 9 、8  、、、、

    延迟队列

    延迟队列是指:消息进入队列后,不能被立即消费,达到指定的时间后,方可被消费。

    使用场景如下:

    例如下单后,30分钟不支付,订单取消。

    新用户注册会员后,7天后进行短信问候等

    但是,RabbitMQ中并没有延迟队列的概念,那么我们怎么实现延迟队列呢?

    实现延迟队列可采用:TTL+DLX 也就是消息生存周期+死信队列。

    上述的生产者代码已经实现了延迟队列,我们只需让消费者侦听死信队列即可。

     @ 侦听死信队列,模拟延迟效果。

    using RabbitMQ.Client;
    using RabbitMQ.Client.Events;
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Threading;
    
    namespace RabbitMQConsumer_2
    {
        class Program
        {
            static void Main(string[] args)
            {
                ConnectionFactory factory = new ConnectionFactory();
                factory.HostName = "127.0.0.1"; //主机名
                factory.UserName = "guest";//使用的用户
                factory.Password = "guest";//用户密码
                factory.Port = 5672;//端口号
                factory.VirtualHost = "/"; //虚拟主机
                factory.MaxMessageSize = 1024; //消息最大字节数 
                                               //创建连接
                var connection = factory.CreateConnection();
                //创建通道
                var channel = connection.CreateModel();
    
                //事件基本消费者
                EventingBasicConsumer consumer = new EventingBasicConsumer(channel);
                //接收到消息事件
                consumer.Received += (ch, ea) =>
                {
                    var message = Encoding.UTF8.GetString(ea.Body.ToArray());
    
                    Console.WriteLine($"消费者收到消息: {message}");
                     
                    channel.BasicAck(ea.DeliveryTag, false);  
                };
                //启动消费者 
                string Qname = "MyQueue_DLX";
                channel.BasicConsume(Qname, true, consumer);//开启自动确认
                Console.WriteLine("消费者已启动");
                Console.ReadKey();
                channel.Dispose();
                connection.Close();
            }
        }
    }

    幂等性

    何为幂等性?

    在MQ中,消费多条相同的消息和消费一条该消息得到相同的结果。

    实际场景中,例如转账,支付送积分等,都需要保障幂等性。

    小明给大牛转了500块钱,由于网络问题,消息消费失败,重发了该消息,那么我们要扣大牛两次金额吗?这显然是不行的,因此,某些场景中,幂等性格外重要。

    网上有很多保证幂等性的方案,例如通过乐观锁版本号来控制等,总之,自行必应吧,网上有很多方案。

    @天才卧龙的博客

  • 相关阅读:
    finalshell连接工具
    oracle11g R2数据库的迁移(同windows系统迁移)使用RMAN
    《python可以这样学》第一章
    python环境开发
    HTTP下帐号密码的截取
    Kail Linux下载与安装
    查看Linux系统内存、CPU、磁盘使用率和详细信息
    内网获取目标的浏览过的所有图片
    修改kali软件源并配置网络
    Python迭代器与生成器
  • 原文地址:https://www.cnblogs.com/chenwolong/p/RabbitTtlDlx.html
Copyright © 2020-2023  润新知