ps:伪延时队列先卖个关子,我们先了解下延时队列。

一、什么是延时队列

 所谓延时队列是指消息push到队列后,监听的消费者不能第一时间获取消息,需要等到指定时间才能消费。
一般在业务里面需要对某些消息做定时发送,不想走定时任务或者是用户下单之后多长时间自动失效类似的场景可以考虑通过延时队列实现。

二、RabbitMQ实现

MQ本身并不支持直接的延时队列实现,但是我们可以通过RabbitMQ的消息TTL和Dead Letter规则来实现

  1. Time TO Live (TTL):

    RabbitMQ可以针对Queue设置x-expires 或者 针对Message设置 x-message-ttl,来控制消息的生存时间

  2. Dead Letter 死信

    RabbitMQ官网这样定义死信消息:

  • . 消息被拒绝(basic.reject或basic.nack)并且requeue=false.
  • . 消息TTL过期
  • 队列达到最大长度(队列满了,无法再添加数据到mq中)
  1. Dead Letter Exchanges(DLX)死信交换机

    MQ默认的死信消息是丢弃的,但是我们可以通过设置以下两个属性让死信消息转发到我们指定的队列。
  • x-dead-letter-exchange:出现dead letter之后将dead letter重新发送到指定exchange
  • x-dead-letter-routing-key:出现dead letter之后将dead letter重新按照指定的routing-key发送
  1. 延时队列实现:

    了解了MQ队列的TTL和Dead Letter之后,我们就可以通过这两个特性来实现,首先我们通过设置消息或者队列的TTL来设置消息在指定时间后成为死信,再设置死信消息的路由转发规则到特定队列,消费者通过监听这个特定队列就能实现延时队列的效果。

  2. 代码实现

生产者发送消息:ttlQueue存放过期时间的队列,deadLetterQueue死信转发队列,seconds是过期时间

 public static void sendTTLMsg(String ttlQueue, String deadLetterQueue, Object msg, Integer seconds) {
MqSender.getInstance().setHost(RABBIT_MQ_HOST);
// 获取到连接以及MQ通道
Connection connection;
try {
connection = MqSender.getInstance().newConnection();
// 从连接中创建通道
Channel channel = connection.createChannel();
// 配置
Map<String, Object> args = new HashMap<String, Object>();
args.put("x-dead-letter-exchange", "");
args.put("x-dead-letter-routing-key", deadLetterQueue);
channel.queueDeclare(deadLetterQueue, true, false, false, null);
channel.queueDeclare(ttlQueue, true, false, false, args);
// 发送消息
channel.basicPublish("", ttlQueue, new AMQP.BasicProperties.Builder().expiration(String.valueOf(seconds)).build(), MAPPER.writeValueAsBytes(msg));
channel.close();
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}

消费者通过监听deadLetterQueue来实现延时消息监听

三、 延时队列的问题

通过我们测试发现,这种方式实现的延时队列,在队列设置TTL的情况下是可以正常的,但是如果根据消息设置了不同的TTL,就会有问题,因为MQ本质上还是消息队列中间件,队列是遵循先进先出的,如果有两个消息先后入队,但是后入队的消息TTL小于前面的消息,它必须等待之前的消息被消费完后才能挪到队列头部,这样不同延时消息就会出现问题。

通过RabbitMQ官网的文档也介绍了这个问题:

 Only when expired messages reach the head of a queue will they actually be discarded (or dead-lettered)

所以我才称之为MQ的伪延时队列,这种延时队列在消息TTL不同的情况下并不能实现真正的延时消费。

四、解决RabbitMQ的伪延时方案

既然RabbitMQ无法支持不同TTL消息的延时消费,那么如果我们要实现这种功能,有什么方案呢,在实际业务开发中,我们有这样的解决方案:

首先我们会创建多级延时消费队列(比如两分钟,三十分钟,一天三种,具体可以根据业务量和访问量还有时间精确度来划分,这里的两分钟、三十分钟是指队列统一的TTL),push消费队列的时候,会根据需要延时的时间,丢到不同的消费队列,比如小于三十分钟的我们push到两分钟队列,三十分钟到一天的放入三十分钟队列,超过一天的放入一天队列,在死信队列的监听器做同样的判断,如果是小于等于当前时间消息的,立马消费,否则按照上述规则继续循环到不同的延时队列

这种方案解决了多级延时消费的问题,并且能够较大程度地避免了消息的重复循环,降低MQ的压力,但是缺点也比较明显,因为最低是两分钟的延时,理论上来说最多会有两分钟的误差,如果对时间要求性比较高的,可以适当调低最低一级别的延时TTL,比如一分钟或者三十秒

类似代码如下:cts是需要消费掉的时间戳

 long now = System.currentTimeMillis();
long cts = Long.valueOf(feedComment.getCts());
if (cts - now <= 30 * 60 * 1000) {
MqSender.sendTTLMsg(MqConstants.FEED_COMMENT_DELAY_QUEUE_2MIN, MqConstants.FEED_COMMENT_AUTO_POST_QUEUE, feedComment, 2 * 60);
} else if (cts - now <= 24 * 60 * 60 * 1000) {
MqSender.sendTTLMsg(MqConstants.FEED_COMMENT_DELAY_QUEUE_30MIN, MqConstants.FEED_COMMENT_AUTO_POST_QUEUE, feedComment, 30 * 60);
} else {
MqSender.sendTTLMsg(MqConstants.FEED_COMMENT_DELAY_QUEUE_24HOUR, MqConstants.FEED_COMMENT_AUTO_POST_QUEUE, feedComment, 24 * 60 * 60);
}

RabbitMQ:伪延时队列的更多相关文章

  1. 基于rabbitMQ 消息延时队列方案 模拟电商超时未支付订单处理场景

    前言 传统处理超时订单 采取定时任务轮训数据库订单,并且批量处理.其弊端也是显而易见的:对服务器.数据库性会有很大的要求,并且当处理大量订单起来会很力不从心,而且实时性也不是特别好 当然传统的手法还可 ...

  2. rabbitMq实现延时队列

    原文:https://my.oschina.net/u/3266761/blog/1926588 rabbitMq是受欢迎的消息中间件之一,相比其他的消息中间件,具有高并发的特性(天生具备高并发高可用 ...

  3. RabbitMq 实现延时队列-Springboot版本

    rabbitmq本身没有实现延时队列,但是可以通过死信队列机制,自己实现延时队列: 原理:当队列中的消息超时成为死信后,会把消息死信重新发送到配置好的交换机中,然后分发到真实的消费队列: 步骤: 1. ...

  4. 【日常摘要】- RabbitMq实现延时队列

    简介 什么是延时队列? 一种带有延迟功能的消息队列 过程: 使用场景 比如存在某个业务场景 发起一个订单,但是处于未支付的状态?如何及时的关闭订单并退还库存? 如何定期检查处于退款订单是否已经成功退款 ...

  5. rabbitmq实现延时队列(死信队列)

    基于队列和基于消息的TTL TTL是time to live 的简称,顾名思义指的是消息的存活时间.rabbitMq可以从两种维度设置消息过期时间,分别是队列和消息本身. 队列消息过期时间-Per-Q ...

  6. Rabbitmq的延时队列的使用

    配置: spring: rabbitmq: addresses: connection-timeout: username: guest password: guest publisher-confi ...

  7. RabbitMQ及延时队列

    一.简介 我用过RabbirMQ的发布订阅模式,以及一对一的延迟队列. 1.RabbitMQ的有消息确认机制,消费一条则队列中少一条,也有对应的消费到消息及认为是消费成功这样的模式,一般使用前者. 发 ...

  8. rabbitmq 安装延时队列插件rabbitmq-delayed-message-exchange

    1.下载rabbitmq-delayed-message-exchange(注意版本对应) 链接:https://github.com/rabbitmq/rabbitmq-delayed-messag ...

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

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

随机推荐

  1. promise以及async、await学习总结

    Promise/async.await帮我们解决了什么 它给我们提供了一种新的异步编程解决方案,同时避免了困扰已久的回调地狱 // 异步的处理可能会产生这样的回调地狱(第二个异步操作和第一个异步的结果 ...

  2. 拒绝从入门到放弃_《Openstack 设计与实现》必读目录

    目录 目录 关于这本书 必看知识点 最后 关于这本书 <Openstack 设计与实现>是一本非常值得推荐的书,为数不多的 Openstack 开发向中文书籍中的精品.如果希望从事 Ope ...

  3. CodeIgniter 技巧 - 通过 Composer 安装 CodeIgniter 框架并安装依赖包

    PHP 项目中,通过 Composer 来管理各种依赖包,类似 Java 中的 Maven,或 Node 中的 npm.CodeIgniter 框架要想通过 Composer 自动加载包也很简单,步骤 ...

  4. Marriage Match II 【HDU - 3081】【并查集+二分答案+最大流】

    题目链接 一开始是想不断的把边插进去,然后再去考虑我们每次都加进去边权为1的边,直到跑到第几次就没法继续跑下去的这样的思路,果不其然的T了. 然后,就是想办法咯,就想到了二分答案. 首先,我们一开始处 ...

  5. Support Vector Machine(3):Soft Margin 平衡之美

    很多材料上面讲道“引入Soft Margin的原因是因为数据线性不可分”,个人认为有些错误,其实再难以被分解的数据,如果我们用很复杂的弯弯绕曲线去做,还是可以被分解,并且映射到高维空间后认为其线性可分 ...

  6. Python3-问题整理

    TypeError: a bytes-like object is required, not 'str' json.decoder.JSONDecodeError: Extra data json文 ...

  7. 01 - Jmeter4.x环境安装以及简单使用

    Jmeter 介绍 Apache JMeter应用程序是开源软件,旨在为负载测试功能行为和测量性能的100%纯Java应用程序.它最初是为测试Web应用程序而设计的,但后来扩展到其他测试功能 常用压力 ...

  8. [fw]拦截系统调用

    今天在ubuntu中玩了下“拦截系统调用”,记录下自己对整个实现的理解. 原理 在linux kernel中,系统调用都放在一个叫做“sys_call_table”的分配表里面,在进入一个系统调用的最 ...

  9. [fw]Real Mode addressing

    Real Mode 在 real mode 中,memory 的使用被限制在 1 MByte(220 bytes) 內,可用的 address 範圍為 0x00000 ~ 0xFFFFF. 由 mem ...

  10. 2019 Multi-University Training Contest 1 - 1004 - Vacation - 二分 - 思维

    http://acm.hdu.edu.cn/showproblem.php?pid=6581 一开始想了好几个假算法.但是启发了一下潘哥,假如时间知道的话就可以从头开始确定各个车的位置.那么直接 \( ...