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

学无止境,精益求精

上一节介绍了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死信队列(死信交换机)

NetCore RabbitMQ 高级特性 消息存活周期TTL、死信交换机/死信对列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绑定队列

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

 

 

 开启代码模式

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、创建死信队列 

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

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

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

5、消息发送

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

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

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

10 、 9 、8  、、、、

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

延迟队列

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

使用场景如下:

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

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

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

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

@天才卧龙的博客