过期时间

RabbitMQ可以为消息和队列设置过期时间Time To Live(TTL)。其目的即过期。

消息过期时间

消息存储在队列中时,如果想为其设置一个有限的生命周期,而不是一直存储着,可以为其设置过期时间。比如,一条消息,我想要三分钟内有效,三分钟后再接收到该消息就算过时了,如果在队列中存储已经超过三分钟,消费者再去接收就是过时了,那便没有意义了。

为消息设置过期时间可以从两方面着手,一是为消息本身设置过期时间,二是为消息的承载体队列设置过期时间。两者同时设置情况下取最短生命周期。

为消息设置

在BasicPublish方法中,可以设置BasicProperties中的Expiration来设置过期时间(单位为毫秒)。

var connFactory = new ConnectionFactory
{
    HostName = "xxx.xxx.xxx.xxx",
    Port = 5672,
    UserName = "rabbitmqdemo",
    Password = "rabbitmqdemo@test",
    VirtualHost = "rabbitmqdemo"
};
using (var conn = connFactory.CreateConnection())
{
    using (var channel = conn.CreateModel())
    {
        var queueName = "messagettl_queue";
        channel.QueueDeclare(queue: queueName, durable: false, exclusive: false, autoDelete: false, arguments: null);         while (true)
        {
            Console.WriteLine("消息内容(exit退出):");
            var message = Console.ReadLine();
            if (message.Trim().ToLower() == "exit")
            {
                break;
            }             var body = Encoding.UTF8.GetBytes(message);
            var basicProperties = channel.CreateBasicProperties();
            basicProperties.Expiration = "10000";//10秒
            channel.BasicPublish(exchange: "", routingKey: queueName, basicProperties: basicProperties, body: body);
            Console.WriteLine("消息内容发送完毕:" + message);
        }
    }
}

如此一来,设置消息生命周期为10秒,当超过该时间后,再由消费者去获取该消息,则获取不到,可以直接从Web界面看到,经过10秒后,消息过期(未启动消费者)。

为队列设置

为每个消息设置过期时间可能不符合一些特定的场景,当需要设定特定队列中的消息都是指定的过期时间时,可以为队列中的消息统一设置过期时间。

队列声明时可以指定参数,其中设置x-message-ttl参数。

var arguments = new Dictionary<string, object>
{
    { "x-message-ttl", 10000 }
};
channel.QueueDeclare(queue: queueName, durable: false, exclusive: false, autoDelete: false, arguments: arguments);

如此一来,该队列发送消息时,如消息本身没有设置过期时间,则使用队列的过期时间。

生产者发送的7条消息过期时间都为10s,一段时间后,消息全部过期。

当ttl设置为0时,仅当消息能够立即被消费,否则消息立马过期,Web面板中只能见到消息发送,队列中没有消息,都被立马过期了。

过期策略

为队列中的消息统一设置过期时间时,当超出了过期时间,消息立马过期挪出队列。先发送的消息在队列头部,先过期的也在队列头部,因此可从头部扫描清除过期消息。

而为直接为消息设置过期时间时,各个消息的过期时间不尽相同,扫描时得队列全局扫描才能识别哪些消息是过期的。

因此,设定在投递给消费者前判断是否过期,超出过期时间消息仍在队列中。

队列过期时间

此处的队列过期时间与消息中的为队列设置过期时间不同,此处是为队列本身考虑,队列自身没有消费者超过一段时间内且没有重新生命该队列,则无需考虑存在。

队列声明时指定参数,其中设置x-expires参数,需大于0,单位毫秒。

var arguments = new Dictionary<string, object>
{
    { "x-expires", 20000 }
};
channel.QueueDeclare(queue: queueName, durable: false, exclusive: false, autoDelete: false, arguments: arguments);

生产者启动应用创建队列,发送消息。



空闲20s不发送消息后队列被删除,再次发送消息匹配不到队列,消息被回退。

死信队列

死信交换机即Dead-Letter-Exchange(DLX),和备份交换机一样,没有什么特殊,只是属性上标记了下,其绑定的队列称之为死信队列。当消息在一个队列中变成死信之后,被重发到死信交换机,存储到死信队列中。

消息变为死信情况

  • 消息被拒绝且不重入队列
  • 消息超出过期时间
  • 队列达到最大长度

设置死信队列

声明队列时可以给定参数,其中设置x-dead-letter-exchange来指明该队列对应的死信交换机。

//死信交换机和死信队列
var dlxExchangeName = "dlx_exchange";
channel.ExchangeDeclare(exchange: dlxExchangeName, type: "fanout", durable: false, autoDelete: false, arguments: null);
var dlxQueueName = "dlx_queue";
channel.QueueDeclare(queue: dlxQueueName, durable: false, exclusive: false, autoDelete: false, arguments: null);
channel.QueueBind(queue: dlxQueueName, exchange: dlxExchangeName, routingKey: ""); //常规队列
var queueName = "nornalmessage_queue";
var arguments = new Dictionary<string, object>
{
    { "x-message-ttl", 10000},
    { "x-dead-letter-exchange", dlxExchangeName }
};
channel.QueueDeclare(queue: queueName, durable: false, exclusive: false, autoDelete: false, arguments: arguments);

如下可见到,生产者初期发送的消息过期后经死信交换机路由进入到死信队列中,后期发送的暂未过期的消息仍在原队列中。当有该部分过期消息的需要时,消费者可以监听死信队列获取消息。

当死信交换机的类型为direct时,可以指定RoutingKey(不指定默认使用原队列RoutingKey)。

可在声明常规队列的属性中设置x-dead-letter-routing-key,以能够匹配上死信交换机与死信队列绑定时的routingkey。

//死信交换机和死信队列
var dlxExchangeName = "dlxroutingkey_exchange";
channel.ExchangeDeclare(exchange: dlxExchangeName, type: "direct", durable: false, autoDelete: false, arguments: null);
var dlxQueueName1 = "dlx_queue1";
channel.QueueDeclare(queue: dlxQueueName1, durable: false, exclusive: false, autoDelete: false, arguments: null);
channel.QueueBind(queue: dlxQueueName1, exchange: dlxExchangeName, routingKey: "waring");
var dlxQueueName2 = "dlx_queue2";
channel.QueueDeclare(queue: dlxQueueName2, durable: false, exclusive: false, autoDelete: false, arguments: null);
channel.QueueBind(queue: dlxQueueName2, exchange: dlxExchangeName, routingKey: "info");
var dlxQueueName3 = "dlx_queue1";
channel.QueueDeclare(queue: dlxQueueName3, durable: false, exclusive: false, autoDelete: false, arguments: null);
channel.QueueBind(queue: dlxQueueName3, exchange: dlxExchangeName, routingKey: "error"); //常规队列
var queueName = "normalmessage_queue";
var arguments = new Dictionary<string, object>
{
    { "x-message-ttl", 10000},
    { "x-dead-letter-exchange", dlxExchangeName },
    { "x-dead-letter-routing-key", "info" }
};
channel.QueueDeclare(queue: queueName, durable: false, exclusive: false, autoDelete: false, arguments: arguments);

当消息过期时,死信交换机根据常规队列绑定的routingkey,匹配到相应的死信队列存储。先发送的消息过期后存入到对应队列中,后续暂未过期消息仍然保持在原队列。

延迟队列

延迟队列算是死信队列的一种应用场景,其本身并不在RabbitMQ或是AMQP协议中有所体现。当不想消费者立马获取到消息,而是等待一段时间才让消费者消费时,比如订单限时支付,超出时间未支付则取消订单,那么可以使用到延迟队列来处理这一场景。

消息过期时间

发送消息时设置过期时间场景下,消息可能并不会在过期后立马从队列中删除,而是要等到消费时候才会判断该消息是否过时。当出现以下场景时则会有点问题。

第一个消息的过期时间很长,而后续的消息的过期时间很短,后续的消息过期后不会立马删除,而是要等到第一个消息过期删除后才会被删除,那么对应延迟队列来说会有点问题,时间超出了设定的延迟时间。

注:对队列设定消息过期时间不存在该问题。

解决方案

RabbitMQ提供了延迟队列的插件,提供延迟队列类型交换机,其不会根据第一个消息是否过期来判断,解决了如上提到的第一个没有过期,后续消息过期的场景,不会受消息先后顺序的影响,而是关注过期时间,先过期的先发送。

生产者代码

声明延迟交换机时类型使用x-delayed-message,需要注意声明交换机类型时需要给定参数x-delayed-type,至于值是哪种类型可依据匹配队列的需要选择。延迟交换机和延迟队列都需要持久化。消息发送时需要使用消息头并设置x-delay来设置延迟时间。

var connFactory = new ConnectionFactory
{
    HostName = "xxx.xxx.xxx.xxx",
    Port = 5672,
    UserName = "rabbitmqdemo",
    Password = "rabbitmqdemo@test",
    VirtualHost = "rabbitmqdemo"
};
using (var conn = connFactory.CreateConnection())
{
    using (var channel = conn.CreateModel())
    {
        //延迟交换机
        var delayExchangeName = "delay_exchange";
        var delayArguments = new Dictionary<string, object>
        {
            { "x-delayed-type", "direct" } //x-delayed-type必须,否则启动报错
        };
        channel.ExchangeDeclare(exchange: delayExchangeName, type: "x-delayed-message", durable: true, autoDelete: false, arguments: delayArguments); //持久化必须,否则启动报错         //延迟队列
        var delayQueueName = "delay_queue";
        channel.QueueDeclare(delayQueueName, durable: true, exclusive: false, autoDelete: false);//持久化必须,否则启动报错
        channel.QueueBind(queue: delayQueueName, exchange: delayExchangeName, routingKey: delayQueueName);         channel.BasicReturn += new EventHandler<RabbitMQ.Client.Events.BasicReturnEventArgs>((sender, e) =>
        {
            var message = Encoding.UTF8.GetString(e.Body.ToArray());
            Console.WriteLine($"收到回退消息:{message}");
        });         while (true)
        {
            Console.WriteLine("消息内容(exit退出):");
            var message = Console.ReadLine();
            if (message.Trim().ToLower() == "exit")
            {
                break;
            }             var body = Encoding.UTF8.GetBytes(message);
            var basicProperties = channel.CreateBasicProperties();
            basicProperties.Headers = new Dictionary<string, object>
        {
            { "x-delay", message == "aaa" ? 30000 : 10000 }//延时时间从header赋值
        };
            channel.BasicPublish(exchange: delayExchangeName, routingKey: delayQueueName, mandatory: true, basicProperties: basicProperties, body: body);
            Console.WriteLine("消息内容发送完毕:" + message + $" {DateTime.Now}");
        }
    }
}

生产者发送消息,到延迟交换机,消息将在设定的延迟时间后路由到相应的延迟队列。

此处设置了mandatory为true,消息最终到了延迟队列,但又被回退到了生产者,实际上对于该延迟交换机插件并不支持mandatory,官方不建议使用该参数

2022-08-29,望技术有成后能回来看见自己的脚步

.Net Core&RabbitMQ死信队列的更多相关文章

  1. RabbitMQ死信队列另类用法之复合死信

    前言 在业务开发过程中,我们常常需要做一些定时任务,这些任务一般用来做监控或者清理任务,比如在订单的业务场景中,用户在创建订单后一段时间内,没有完成支付,系统将自动取消该订单,并将库存返回到商品中,又 ...

  2. rabbitmq死信队列消息监听

    #邮件通知并发送队列消息#!/bin/bash maillog="/var/log/mq.maillog" message_file="/tmp/mq_message&q ...

  3. RabbitMQ 死信队列 延时

    package com.hs.services.config; import java.util.HashMap; import java.util.Map; import org.springfra ...

  4. RabbitMQ死信队列

    关于RabbitMQ死信队列 死信队列 听上去像 消息“死”了     其实也有点这个意思,死信队列  是 当消息在一个队列 因为下列原因: 消息被拒绝(basic.reject/ basic.nac ...

  5. springboot rabbitmq 死信队列应用场景和完整demo

    何为死信队列? 死信队列实际上就是,当我们的业务队列处理失败(比如抛异常并且达到了retry的上限),就会将消息重新投递到另一个Exchange(Dead Letter Exchanges),该Exc ...

  6. 【RabbitMQ】一文带你搞定RabbitMQ死信队列

    本文口味:爆炒鱿鱼   预计阅读:15分钟 一.说明 RabbitMQ是流行的开源消息队列系统,使用erlang语言开发,由于其社区活跃度高,维护更新较快,性能稳定,深得很多企业的欢心(当然,也包括我 ...

  7. 【MQ中间件】RabbitMQ -- RabbitMQ死信队列及内存监控(4)

    1.RabbitMQ TTL及死信队列 1.1.TTL概述 过期时间TTL表示可以对消息设置预期的时间,在这个时间内都可以被消费者接收获取:过了之后消息将自动被删除.RabbitMQ可以对消息和队列设 ...

  8. rabbitmq死信队列和延时队列的使用

    死信队列&死信交换器:DLX 全称(Dead-Letter-Exchange),称之为死信交换器,当消息变成一个死信之后,如果这个消息所在的队列存在x-dead-letter-exchange ...

  9. RabbitMq死信队列(接盘侠)

    队列创建之后,后期对其修改或者参数添加会报错.需要把队列重新删除,重新创建线上环境不能把队列删除,优雅安全的方式是重新建一个队列,把死信队列相关的队列进行绑定 在有过期时间的队列中设定最大接收能力5条 ...

随机推荐

  1. ACL权限控制

    ALC讲述比较详细 https://zhuanlan.zhihu.com/p/360158311

  2. SpringBoot整合SpringSecurityOauth2实现鉴权-动态权限

    写在前面 思考:为什么需要鉴权呢? 系统开发好上线后,API接口会暴露在互联网上会存在一定的安全风险,例如:爬虫.恶意访问等.因此,我们需要对非开放API接口进行用户鉴权,鉴权通过之后再允许调用. 准 ...

  3. SAP Easy tree

    *&---------------------------------------------------------------------* *& Include SIMPLE_T ...

  4. bat-windows系统激活

    激活命令 slmgr -ipk M7XTQ-FN8P6-TTKYV-9D4CC-J462D slmgr -skms kms.03k.org slmgr -ato slmgr -dlv 激活异常处理方法 ...

  5. NC16783 [NOIP1998]拼数

    NC16783 [NOIP1998]拼数 题目 题目描述 设有 \(n\) 个正整数(\(n ≤ 20\)),将它们联接成一排,组成一个最大的多位整数. 例如:\(n=3\) 时,\(3\) 个整数 ...

  6. ACM组合计数入门

    1 排列组合 1.1 排列 \[A_n^m=n(n-1)(n-2)\cdots(n-m+1)=\frac{n!}{(n-m)!} \] 定义:从 n 个中选择 m 个组成有序数列,其中不同数列的数量. ...

  7. Training a classifier

    你已经学习了如何定义神经网络,计算损失和执行网络权重的更新. 现在你或许在思考. What about data? 通常当你需要处理图像,文本,音频,视频数据,你能够使用标准的python包将数据加载 ...

  8. 基于MATLAB静态目标分割的药板胶囊检测

    一.目标 1 将药板从黑色背景中分离(药板部分显示为白色,背景显示为黑色): 2 根据分割结果将药板旋转至水平: 3 提取药板中的药丸的位置信息: 二.方法描述 处理图像如下: (1)首先将图像转为灰 ...

  9. 对象数组和对象对象数组的for-each循环

    对象数组的声明 类名称 对象数组名[] = null: 对象数组名 = new 类名称[长度] 定义并开辟数组 类名称 对象数据名[] = new 类名称[长度]; 在声明一个对象数组后,必须对每个数 ...

  10. 【ASP.NET Core】自定义的配置源

    本文的主题是简单说说如何实现 IConfigurationSource.IConfigurationProvider 接口来自定义一个配置信息的来源,后面老周给的示例是实现用 CSV 文件进行应用配置 ...