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

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

@天才卧龙的博客

 

posted @ 2022-07-26 14:14  天才卧龙  阅读(298)  评论(1编辑  收藏  举报