概念:

消息的TTL(Time To Live)
消息的TTL就是消息的存活时间。RabbitMQ可以对队列和消息分别设置TTL。对队列设置就是队列没有消费者连着的保留时间,也可以对每一个单独的消息做单独的设置。超过了这个时间,我们认为这个消息就死了,称之为死信。
如果队列设置了,消息也设置了,那么会取小的。所以一个消息如果被路由到不同的队列中,这个消息死亡的时间有可能不一样(不同的队列设置)。这里单讲单个消息的TTL,因为它才是实现延迟任务的关键。
可以通过设置消息的expiration字段或者x-message-ttl属性来设置时间,两者是一样的效果。
消息扔到队列中后,过了设置的限定时间,如果没有被消费,它就死了。不会被消费者消费到。这个消息后面的,没有“死掉”的消息对顶上来,被消费者消费。
死信在队列中并不会被删除和释放,它会被统计到队列的消息数中去。单靠死信还不能实现延迟任务,还要靠Dead Letter Exchange。

Dead Letter Exchanges
Exchage的概念在这里就不在赘述,可以从这里进行了解。一个消息在满足如下条件下,会进死信路由,记住这里是路由而不是队列,一个路由可以对应很多队列。
1. 一个消息被Consumer拒收了,并且reject方法的参数里requeue是false。也就是说不会被再次放在队列里,被其他消费者使用。
2. 上面的消息的TTL到了,消息过期了。
3. 队列的长度限制满了。排在前面的消息会被丢弃或者扔到死信路由上。
Dead Letter Exchange其实就是一种普通的exchange,和创建其他exchange没有两样。只是在某一个设置Dead Letter Exchange的队列中有消息过期了,会自动触发消息的转发,发送到Dead Letter Exchange中去。

实现延迟队列:
延迟任务通过消息的TTL和Dead Letter Exchange来实现。我们需要建立2个队列,一个用于发送消息,一个用于消息过期后的转发目标队列。

发布者Code:

        public void PublishDelayMessage<T>(T message, int expireMinutes, bool durable = true) where T : class
{
if (expireMinutes <= )
{
throw new ArgumentException("expireMinutes 必须大于0");
} using (var channel = connection.CreateModel())
{
var body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message)); //创建默认的死信交换机
string receiveExchangeName = $"{typeof(T).FullName}.DelayReceive";
string receiveQueueName = $"{receiveExchangeName}.{expireMinutes}";
channel.ExchangeDeclare(exchange: receiveExchangeName, type: "direct", durable: durable); string bufferExchange = $"{typeof(T).FullName}.DelayBuffer";
string bufferQueueName = $"{bufferExchange}.{expireMinutes}";
channel.ExchangeDeclare(exchange: bufferExchange, type: "direct", durable: durable); //创建消息缓冲队列,在这个队列里面实现消息的过期转发
var properties = channel.CreateBasicProperties();
//properties.Expiration = (expireMinutes * 60000).ToString();
properties.Expiration= (expireMinutes * ).ToString();
Dictionary<string, object> arguments = new Dictionary<string, object>();
arguments.Add("x-dead-letter-exchange", receiveExchangeName);
arguments.Add("x-dead-letter-routing-key", receiveQueueName);
channel.QueueDeclare(queue: bufferQueueName, durable: durable, exclusive: false, autoDelete: false, arguments: arguments);
channel.QueueBind(queue: bufferQueueName, exchange: bufferExchange, routingKey: bufferQueueName); //这个队列用于消息在缓冲队列中过期后转发的目标队列
channel.QueueDeclare(queue: receiveQueueName, durable: durable, exclusive: false, autoDelete: false, arguments: null);
channel.QueueBind(queue: receiveQueueName, exchange: receiveExchangeName, routingKey: receiveQueueName); channel.BasicPublish(exchange: bufferExchange, routingKey: bufferQueueName, basicProperties: properties, body: body);
}
}

消费订阅:

  /// <summary>
/// 订阅延迟消息
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="onMessage"></param>
/// <param name="expireMinutes"></param>
/// <param name="durable"></param>
/// <returns></returns>
public bool SubscribeDelayMessage<T>(Action<T> onMessage, int expireMinutes, bool durable = true) where T : class
{
if (expireMinutes <= )
{
throw new ArgumentException("expireMinutes 必须大于0");
} var channel = connection.CreateModel(); string receiveExchangeName = $"{typeof(T).FullName}.DelayReceive";
string receiveQueueName = $"{receiveExchangeName}.{expireMinutes}";
channel.ExchangeDeclare(exchange: receiveExchangeName, type: "direct", durable: durable); channel.QueueDeclare(queue: receiveQueueName, durable: durable, exclusive: false, autoDelete: false, arguments: null);
channel.QueueBind(queue: receiveQueueName, exchange: receiveExchangeName, routingKey: receiveQueueName); var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
try
{
var message = Encoding.UTF8.GetString(ea.Body);
onMessage(JsonConvert.DeserializeObject<T>(message));
}
catch
{ }
};
channel.BasicConsume(queue: receiveQueueName, noAck: true, consumer: consumer);
return true;
}

虽然没贴出全部的代码,但是最核心的已经有了

1,设置消息的过期时间

2.设置缓冲队列,并且在消息过期以后转发到真实的路由中

看Wireshark抓包分析:

1.过期时间

可以看到发布消息的properties里面设置了expiration

2.过期转发

可以看到缓冲队列在声明的时候,设置了arguments

里面配置了x-dead-letter-exchange,x-dead-letter-routing-key

这样缓冲队列里面的消息过期以后,就将消息转发给配置的对应配置的交换机路由。

优化,上面的版本中,延时的时间不同,就必须要创建新的死信交换机,死信消息队列,接受交换机,接受消息队列,这样大大的浪费了资源

现在对其进行优化:

 /// <summary>
/// 发布延迟消息,当createSpecifyReceiveQueue=true时,延迟订阅参数expireMinutes必须与本方法的expireMinutes保持一致
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="message"></param>
/// <param name="expireMinutes"></param>
/// <param name="createSpecifyReceiveQueue"></param>
/// <param name="durable"></param>
public void PublishDelayMessage<T>(T message, int expireMinutes, bool createSpecifyReceiveQueue = false, bool durable = true) where T : class
{
if (expireMinutes <= )
{
throw new ArgumentException("expireMinutes 必须大于0");
} using (var channel = connection.CreateModel())
{
var body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message)); //创建默认的死信交换机
string receiveExchangeName = $"{typeof(T).FullName}.DelayReceive";
string receiveQueueName = "";
//创建指定的接收队列
if (createSpecifyReceiveQueue)
{
receiveQueueName = $"{receiveExchangeName}.{expireMinutes}";
}
else
{
receiveQueueName = $"{receiveExchangeName}.DelayDefaultReceive";
}
channel.ExchangeDeclare(exchange: receiveExchangeName, type: "direct", durable: durable); string bufferExchange = $"{typeof(T).FullName}.DelayBuffer";
string bufferQueueName = "";
if (createSpecifyReceiveQueue)
{
bufferQueueName = $"{bufferExchange}.{expireMinutes}";
}
else
{
bufferQueueName = $"{bufferExchange}.Default.{expireMinutes}";
}
channel.ExchangeDeclare(exchange: bufferExchange, type: "direct", durable: durable); //创建消息缓冲队列,在这个队列里面实现消息的过期转发
var properties = channel.CreateBasicProperties();
properties.Expiration = (expireMinutes * ).ToString();
Dictionary<string, object> arguments = new Dictionary<string, object>();
arguments.Add("x-dead-letter-exchange", receiveExchangeName);
arguments.Add("x-dead-letter-routing-key", receiveQueueName);
channel.QueueDeclare(queue: bufferQueueName, durable: durable, exclusive: false, autoDelete: false, arguments: arguments);
channel.QueueBind(queue: bufferQueueName, exchange: bufferExchange, routingKey: bufferQueueName); //这个队列用于消息在缓冲队列中过期后转发的目标队列
channel.QueueDeclare(queue: receiveQueueName, durable: durable, exclusive: false, autoDelete: false, arguments: null);
channel.QueueBind(queue: receiveQueueName, exchange: receiveExchangeName, routingKey: receiveQueueName); channel.BasicPublish(exchange: bufferExchange, routingKey: bufferQueueName, basicProperties: properties, body: body);
}
} /// <summary>
/// 订阅延迟消息,注意:如果expireMinutes>0,发布的延迟消息的参数createSpecifyReceiveQueue必须设置为true
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="onMessage"></param>
/// <param name="expireMinutes"></param>
/// <param name="durable"></param>
/// <returns></returns>
public bool SubscribeDelayMessage<T>(Action<T> onMessage, int expireMinutes = , bool durable = true) where T : class
{
var channel = connection.CreateModel(); string receiveExchangeName = $"{typeof(T).FullName}.DelayReceive";
string receiveQueueName = "";
channel.ExchangeDeclare(exchange: receiveExchangeName, type: "direct", durable: durable);
if (expireMinutes <= )
{
receiveQueueName = $"{receiveExchangeName}.DelayDefaultReceive";
channel.QueueDeclare(queue: receiveQueueName, durable: durable, exclusive: false, autoDelete: false, arguments: null);
}
else
{
receiveQueueName = $"{receiveExchangeName}.{expireMinutes}";
channel.QueueDeclare(queue: receiveQueueName, durable: durable, exclusive: false, autoDelete: false, arguments: null);
}
channel.QueueBind(queue: receiveQueueName, exchange: receiveExchangeName, routingKey: receiveQueueName); var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
try
{
var message = Encoding.UTF8.GetString(ea.Body);
onMessage(JsonConvert.DeserializeObject<T>(message));
}
catch
{ }
};
channel.BasicConsume(queue: receiveQueueName, noAck: true, consumer: consumer);
return true;
}

RabbitMQ延时任务的更多相关文章

  1. RabbitMQ 延时消息队列

    消息延时在日常随处可见: 1.订单创建10min之后不发起支付,自动取消. 2.30min定时推送一次邮件信息. 最常用到方式后台定时任务轮训,量小的时候可以使用,量大会出现数据读取会性能问题.Rab ...

  2. rabbitmq 延时队列

    前言 某个产品 或者订单,有个有效期 过了有效期要取消 方法一 : 写个脚本,用crontab 定时扫描 改变状态 但是最低只能一分钟 ,不适合 方法二 : 用swoole得毫秒定时器,每秒钟去扫描表 ...

  3. RabbitMQ 延时消息设计

    问题背景 所谓"延时消息"是指当消息被发送以后,并不想让消费者立即拿到消息,而是等待指定时间后,消费者才拿到这个消息进行消费. 场景一:客户A在十二点下了一个订单,我想半个小时后来 ...

  4. java实现rabbitMQ延时队列详解以及spring-rabbit整合教程

    在实际的业务中我们会遇见生产者产生的消息,不立即消费,而是延时一段时间在消费.RabbitMQ本身没有直接支持延迟队列功能,但是我们可以根据其特性Per-Queue Message TTL和 Dead ...

  5. rabbitMq延时消息分级别

    做支付平台的时候.需要实现接受上游支付消息,通知给下游渠道. 针对下游渠道:要实现 按通知次数 递进 延时通知 下游渠道的支付/签约/代扣的状态 可参考微信按照 15/15/30/180/1800/1 ...

  6. rabbitmq 延时队列 插件方式实现 每条消息都延时自己时间

    上篇文章的延时是加到队列上的 通过死信过时推送 ,缺点就是不能每条消息定义自己的过时时间而且每次有新的过时时间,要新建一个交换机和队列 https://www.cnblogs.com/brady-wa ...

  7. IOS IAP 自动续订 之 利用rabbitmq延时队列自动轮询检查是否续订成功

    启用针对自动续期订阅的服务器通知: - 官方地址: - https://help.apple.com/app-store-connect/#/dev0067a330b - 相关字段, 相关类型地址:  ...

  8. RabbitMQ延时队列应用场景

    应用场景 我们系统未付款的订单,超过一定时间后,需要系统自动取消订单并释放占有物品 常用的方案 就是利用Spring schedule定时任务,轮询检查数据库 但是会消耗系统内存,增加了数据库的压力. ...

  9. spring boot Rabbitmq集成,延时消息队列实现

    本篇主要记录Spring boot 集成Rabbitmq,分为两部分, 第一部分为创建普通消息队列, 第二部分为延时消息队列实现: spring boot提供对mq消息队列支持amqp相关包,引入即可 ...

随机推荐

  1. Python 目录【持续更新中】

    Python 基础 字符编码 数据类型 文件处理 流程控制 练习题 函数,递归 匿名函数.内置函数 装饰器变形记 函数装饰器 递归 二分法 迭代器和生成器 协程函数 列表表达式 生成器表达式 异常处理 ...

  2. mysql ifnull判断为空设置默认值

    IFNULL(count,0) as count select IFNULL(count,0) as count from table_name 可以设置当某个字段为空的时候默认值.

  3. 聊一聊Linux中的工作队列2

    上一篇文章对工作队列原理以及核心数据结构做了简单介绍,本文重点介绍下workqueue的创建以及worker的管理. 一.工作队列的创建(__alloc_workqueue_key) struct w ...

  4. java-JProfiler(二)-进行本地JVM的性能监控-tomcat

    监视本地的Tomcat, 看似是本地,其实JProfiler GUI在一个单独的JVM里启动,他与被监视的目标jvm之间通过socket通讯,目的为了不干扰目标JVM.所以监视本地Tomcat与监视远 ...

  5. Redis的设计与实现——字典

    参考博客 绝大多数语言中的字典底层实现基本上都是哈希表.哈希表中用 “负载因子” 来衡量哈希表的 空/满 程度.为了让负载因子在一定的合理范围之内,提高查询的性能,一般的做法是让哈希表扩容,然后reh ...

  6. wcf 服务器无法处理请求由于内部错误

    The server was unable to process the request due to an internal error.  For more information about t ...

  7. OnClick,OnClientClick和OnServerClick的区别

       OnClientClick是客户端事件处理方法,一般采用JavaScript来进行处理,也就是直接在IE端运行,一点击就运行 OnClick是服务器端事件处理方法,在服务器端也就是IIS中运行, ...

  8. 【转】eclipse反编译插件

    原文地址:http://bbs.csdn.net/topics/390263414 离线安装包下载地址一:http://feeling.sourceforge.net/downloads/org.sf ...

  9. OVS

    基于 OpenvSwitch的OpenFlow实践 Open vSwitch 概述 OpenvSwitch(简称OVS)是由NiciraNetworks主导的,运行在虚拟化平台(例如 KVM,Xen) ...

  10. linux常用命令:sort 命令

    sort 命令是Linux系统下一种对文件排序的工具,sort 命令功能十分强大,是 Shell 脚本编程是常用的文件排序工具. 1.命令格式: sort [选项]... [文件]... 或:sort ...