RabbitMQ 延迟消息的队头阻塞问题是指,在使用死信队列(DLX)和 TTL(消息过期时间)实现延迟消息时,由于队列的先进先出(FIFO)特性,在队列头部消息未过期的情况下,即使后续消息已经过期也不能及时处理的情况

实现原理

RabbitMQ 延迟消息的实现方式有以下两种:

  1. 死信队列+TTL
  2. 使用 rabbitmq-delayed-message-exchange 插件

而我们本文要讨论的“RabbitMQ 延迟消息的队头阻塞问题”只会发生在死信队列+TTL 的实现方式中。

死信队列 + TTL 的实现流程如下:

  1. 生产者先将设置了 TTL(过期时间)的消息发送到普通队列。
  2. 普通队列没有消息者,所以一定会过期,消息过期之后就会发送到死信队列。
  3. 消费者订阅死信队列获取消息,并执行延迟任务。

代码实现

死信队列 + TTL 在 Spring Boot 项目中的实现代码如下。

  1. 定义死信交换器(DLX)和死信队列
// Spring Boot 配置示例
@Configuration
public class RabbitConfig {
// 定义死信交换器
@Bean
public DirectExchange dlxExchange() {
return new DirectExchange("dlx.exchange");
} // 定义死信队列
@Bean
public Queue dlxQueue() {
return new Queue("dlx.queue");
} // 绑定死信队列到 DLX
@Bean
public Binding dlxBinding() {
return BindingBuilder.bind(dlxQueue()).to(dlxExchange()).with("dlx.routing.key");
} // 定义普通队列,设置死信交换器和路由键
@Bean
public Queue mainQueue() {
Map<String, Object> args = new HashMap<>();
args.put("x-dead-letter-exchange", "dlx.exchange");
args.put("x-dead-letter-routing-key", "dlx.routing.key");
// 可选:设置队列级别的 TTL(所有消息统一过期时间)
args.put("x-message-ttl", 10000); // 10秒
return new Queue("main.queue", true, false, false, args);
} // 主队列绑定到默认交换器(根据需要调整)
@Bean
public Binding mainBinding() {
return BindingBuilder.bind(mainQueue()).to(new DirectExchange("default.exchange")).with("main.routing.key");
}
}
  1. 发送消息时设置 TTL(消息级别)
// 发送延迟消息(消息级别 TTL)
public void sendDelayedMessage(String message, int delayMs) {
rabbitTemplate.convertAndSend("default.exchange", "main.routing.key", message, msg -> {
// 设置消息过期时间(覆盖队列级别的 TTL)
msg.getMessageProperties().setExpiration(String.valueOf(delayMs));
return msg;
});
}
  1. 消费者监听死信队列
@RabbitListener(queues = "dlx.queue")
public void handleDelayedMessage(String message) {
System.out.println("处理延迟消息: " + message);
}

所以说消息的过期时间 TTL 的设置方式有以下两种:

  1. 队列级别:通过设置队列的 x-message-ttl 参数,设置队列统一的过期时间。
Map<String, Object> args = new HashMap<>();
args.put("x-message-ttl", 60000); // 设置队列消息过期时间为 60 秒
channel.queueDeclare(queueName, true, false, false, args);
  1. 消息级别:通过给每个消息设置 expiration 属性,为每个消息设置过期时间。
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.deliveryMode(2) // 消息持久化
.expiration("60000") // 设置消息过期时间为 60 秒
.build();
channel.basicPublish(exchangeName, routingKey, properties, message.getBytes());

如果同时设置了消息级 TTL 和队列级 TTL,消息的实际过期时间会取两者中的最小值。

造成队头阻塞的原因

造成队头阻塞的原因有以下两个:

  1. 先进先出的队列特性:队列中的消息必须按顺序处理,即使后面的消息 TTL 较短且已过期,也必须等待队头的消息先被处理(或过期)。
  2. TTL 检查机制:RabbitMQ 默认仅在处理队头消息时检查其 TTL,如果队头消息的 TTL 较长(例如 10 分钟),即使后续消息的 TTL 更短(例如 1 分钟),这些消息也会被阻塞,直到队头消息过期或被移除。

如下图所示:

解决方案

  1. 为不同延迟时间创建独立队列:将相同 TTL 的消息放入同一队列,避免消息的过期时间不一致。
  2. 使用延迟插件:使用 RabbitMQ 的延迟插件 rabbitmq_delayed_message_exchange,直接通过延迟交换机实现延迟消息,绕过死信队列的 FIFO 限制。延迟插件是通过将消息存储到内置数据库 Mnesia,再通过不断判断过期消息,实现延迟消息的投递和执行的,因此它不存在队列的先进先出和队头阻塞的问题。

小结

队头阻塞问题是发生在使用死信队列加 TTL 实现 RabbitMQ 延迟消息的场景中,造成的原因是队列先进先出的特性,加上延迟消息的检查机制导致的,我们可以使用 RabbitMQ 的延迟插件来避免此问题。

那么问题来了,使用延迟插件如何实现延迟任务?它和死信队列的实现方式有哪些具体的区别呢?

本文已收录到我的面试小站 www.javacn.site,其中包含的内容有:场景题、并发编程、MySQL、Redis、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、JVM、设计模式、消息队列等模块。

面试官:谈谈RabbitMQ的队头阻塞问题?的更多相关文章

  1. 面试官:"谈谈分库分表吧?"

    原文链接:面试官:"谈谈分库分表吧?" 面试官:“有并发的经验没?”  应聘者:“有一点.”   面试官:“那你们为了处理并发,做了哪些优化?”   应聘者:“前后端分离啊,限流啊 ...

  2. 【Http】队头阻塞(Head of line blocking)多路复用(Multiplexing)

        图中第一种请求方式,就是单次发送request请求,收到response后再进行下一次请求,显示是很低效的. 于是http1.1提出了管线化(pipelining)技术,就是如图中第二中请求方 ...

  3. 面试官:RabbitMQ过期时间设置、死信队列、延时队列怎么设计?

    哈喽!大家好,我是小奇,一位不靠谱的程序员 小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧 文章持续更新 一.前言 RabbitMQ我们经常的使用, ...

  4. 真正“搞”懂HTTP协议07之队头阻塞真的很烦人

    这一篇文章,我们核心要聊的事情就是HTTP的对头阻塞问题,因为HTTP的核心改进其实就是在解决HTTP的队头阻塞.所以,我们会讲的理论多一些,而实践其实很少,要学习的头字段也只有一个,我会在最开始就讲 ...

  5. 面试官:RabbitMQ有哪些工作模式?

    哈喽!大家好,我是小奇,一位不靠谱的程序员 小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧 文章持续更新 一.前言 今天又.又.又来面试了,还是老规 ...

  6. 面试官:RabbitMQ怎么实现消费端限流

    哈喽!大家好,我是小奇,一位不靠谱的程序员 小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧 文章持续更新 一.前言 RabbitMQ有很多高级特性, ...

  7. 『假如我是面试官』RabbitMQ我会这样问

    1. 为什么你们公司选择RabbitMQ作为消息中间件 在消息队列选型时,我们调研了市场上比较常用ActiveMQ,RabbitMQ,RocketMQ,Kafka. RabbitMQ相对成熟稳定,这是 ...

  8. 面试官:如果 http 响应头中 ETag 值改变了,是否意味着文件内容一定已经更改

    本篇文章由我的 一日一题 中的四个 Issue 组合而成 [Q111]http 响应头中的 ETag 值是如何生成的 [Q112]如果 http 响应头中 ETag 值改变了,是否意味着文件内容一定已 ...

  9. 面试官:“谈谈Spring中都用到了那些设计模式?”。

    我自己总结的Java学习的系统知识点以及面试问题,已经开源,目前已经 41k+ Star.会一直完善下去,欢迎建议和指导,同时也欢迎Star: https://github.com/Snailclim ...

  10. 【原创】面试官:谈谈你对mysql联合索引的认识?

    引言 本文预计分为两个部分: (1)联合索引部分的基础知识 在这个部分,我们温习一下联合索引的基础 (2)联合索引部分的实战题 在这个部分,列举几个我认为算是实战中的代表题,挑出来说说. 正文 基础 ...

随机推荐

  1. Threejs入门-灯光

    在 Three.js 中,灯光是非常重要的元素之一,它能够模拟现实世界中的光照效果,帮助我们打造更加真实的三维场景.灯光的种类和配置方式可以影响整个场景的视觉效果,在不同的应用中,灯光的使用非常关键. ...

  2. 【集成-Jedis】SpringBoot集成Jedis

    将jedis的依赖放进Maven <dependency> <groupId>redis.clients</groupId> <artifactId>j ...

  3. MYSQL数据库设计操作规范 ❤️【建议收藏】

    1.背景及其意义 MySQL数据库与 Oracle. SQL Server 等数据库相比,有其内核上的优势与劣势.我们在使用MySQL数据库的时候需要遵循一定规范,扬长避短.本文档旨在帮助或指导数据中 ...

  4. 中电金信鲸视:以AI视觉技术为复杂行业场景装上“火眼金睛”

    ​ 作为人工智能和计算机视觉的交叉领域,智能视觉通过仿生人类视觉机能,对不同形式的视觉输入进行处理.理解和决策.现今,智能视觉已成为应用广泛.市场覆盖大.形式多样的产业方向,得到了国家政策的大力支持. ...

  5. django连接数据库与orm基础知识

    目录 一.django连接数据库 二.pycharm连接MySQL数据库 三.ORM简介 1.ORM概念 2.ORM由来 3.ORM的优势和劣势 ORM的优势 ORM的劣势 四.ORM基本操作 1.现 ...

  6. 比特币源码分析--深入理解区块链16.Base58编码和解码

    比特币源码分析--深入理解区块链16.Base58编码和解码   Base58是比特币中使用的一种独特编码方式,它主要用于比特币的钱包地址,在前面文章已经介绍过如何通过椭圆曲线方程算法,通过私钥来生成 ...

  7. Qt/C++音视频开发72-倍速推流/音视频同步倍速推流/不改变帧率和采样率/低倍速和高倍速

    一.前言 最近多了个新需求,需要倍速推流,推流界的扛把子obs也有倍速推流功能,最高支持到两倍速.这里所说的倍速,当然只限定在文件,只有文件才可能有倍速功能,因为也只有文件才能倍速解码播放.实时视频流 ...

  8. Qt编写地图综合应用7-百度离线地图

    一.前言 离线地图的核心其实就是拿到这些瓦片地图文件,并不是离线地图的代码怎么写,其实离线地图的网页代码和在线地图的网页代码几乎一致的,主要就是将对应的依赖的js文件从在线的地址改成本地的地址,然后可 ...

  9. sql建表添加注释的语句

    官方自带的,我觉得麻烦,稍微'封装'一下 create proc Proc_AddExplian @table varchar(200), @clolum varchar(200), @text va ...

  10. PostgreSQL 数据库的启动与停止管理

    title: PostgreSQL 数据库的启动与停止管理 date: 2024/12/28 updated: 2024/12/28 author: cmdragon excerpt: 作为一个强大的 ...