概念:

消息的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. windows底下怎么让cmder通过输入subl去打开sublime text

    在window自身的cmd或者你安装的cmder中输入 doskey subl="D:\Program Files\sublime3\Sublime Text 3\sublime_text. ...

  2. 第四课:通过配置文件获取对象(Spring框架中的IOC和DI的底层就是基于这样的机制)

    首先在D盘创建一个文件hero.txt,内容为:com.hero.Hero(此处必须是Hero的完整路径) 接下来是Hero类 package com.hero; public class Hero ...

  3. 既生list何生tuple

    python中list是可以修改的;若要创建一系列不可修改的元素时,就需要tuple. 用 tuple 的好处: 1.tuple 比 list 操作速度快.如果您定义了一个值的常量集,并且唯一要用它做 ...

  4. col-md-*和col-sm-*

    屏幕大于(≥992px) ,使用col-md-* 而不是col-sm-*如果屏幕大于(≥768px),小雨<=992px,使用col-sm-* 而不是col-md-*

  5. python开发中容易犯的错误整合

    写在前面 长期更新的博文.多数是一些比较隐蔽的问题.欢迎留言补充. pip并不是那么安逸 pip安装对于开发者来说确实是一种解放.可以自动安装依赖包,但执行最简单的pip安装命令时,并不是所有的依赖都 ...

  6. GET 'https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle/3.1.2/gradle-3

    Could not GET 'https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle/3.1.2/gradle-3 ...

  7. 怎么在jquery里清空文本框的内容

    $("input[name='test']").val("").focus(); // 将name=test的文本框清空并获得焦点,以便重新输入

  8. 用js或css实现淡入淡出

    淡入淡出?你问我有什么用? 提升首页13格的东西,你居然不知道!! 好啦,不废话了,正文. 1 js 主要元素:fadeIn()   fadeOut() show hide 2 css 主要元素: o ...

  9. AOAPC I: Beginning Algorithm Contests -- Training Guide (Rujia Liu) Chapter 3. Data Structures Fundamental Data Structures

    10410 这题说的是给了一棵多叉树的 bfs遍历轨迹 和dfs 遍历 轨迹 在父节点向叶子节点扩展的时候优先遍历编号较小的节点.我还原出这课树特殊判定 根据bfs的顺序来建立这课树,用一个队列安排要 ...

  10. SQL语句--查询任务

    SELECT 目标字段1, 目标字段2 FROM 目标表WHERE ID IN(SELECT 外键 FROM 外键所在表WHERE 提供数据字段 IN(提供数据第一条, 提供数据第二条, ))