十年河东,十年河西,莫欺少年穷
学无止境,精益求精
上一节介绍了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、创建死信队列
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)); //发送消息
等待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块钱,由于网络问题,消息消费失败,重发了该消息,那么我们要扣大牛两次金额吗?这显然是不行的,因此,某些场景中,幂等性格外重要。
网上有很多保证幂等性的方案,例如通过乐观锁版本号来控制等,总之,自行必应吧,网上有很多方案。
@天才卧龙的博客