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. Bing Advanced Search Tricks You Should Know

    Bing is one of the world's most popular search engines that has gained many fans with its ease of us ...

  2. MongoDB数据迁移

    将集合user从192.168.1.12:27017导入到192.168.1.120:27017 数据的导出:mongoexport 数据的导入:mongoimport 导出集合user的过程: [r ...

  3. .NETFramework:System.Net.WebClient.cs

    ylbtech-.NETFramework:System.Net.WebClient.cs 提供用于将数据发送到和接收来自通过 URI 确认的资源数据的常用方法 1.返回顶部 1. #region 程 ...

  4. linux 复制到远程服务器

    scp 文件路径 root@192.168.0.1:文件夹路径 会提示你输入远程服务器密码

  5. javascript 调试 使用console.table()

    或许你已经习惯了console.log()来调试js,非常使用,但是今天微博看到console.table()调试javascript,和console.log()类似,主要区别在于: 主要用来输出对 ...

  6. no suitable ctr exists to convert from 'int' to 'std::basic_string<char,std::char_traits<char>,std::allocator<char> >

    int xfun(int *a,int n) { int x = *a;//a的类型是int *,a+1跳动一个int的长度 ; pa < a + n; pa++)//指向同一个类型的指针比较大 ...

  7. ELMO,BERT和GPT简介

    1.Contextualized Word Embedding 同样的单词有不同的意思,比如下面的几个句子,同样有 “bank” ,却有着不同的意思.但是用训练出来的 Word2Vec 得到 “ban ...

  8. 查询SQLSERVER执行过的SQL记录(历史查询记录)(转)

    原文链接:https://www.cnblogs.com/icycore/p/10493237.html 有的时候,需要知道近段时间SQLSERVER执行了什么语句,可以用下面的方法: SELECT ...

  9. [Python3] 023 面向对象 第三弹

    目录 7. 类相关函数 8. 类的成员描述符(属性) 9. 类的内置属性 10. 类的常用魔术方法 10.1 操作类 10.2 描述符 10.3 属性操作 10.4 运算分类相关魔术方法 接上一篇 [ ...

  10. phpcms批量更新内容页只更新一点就返回问题

    phpcms批量更新内容页只更新一点就返回问题 给caches目录增加写入权限