news 2026/10/3 3:06:26

Spring Boot 1.4接入RabbitMQ延时队列插件实录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot 1.4接入RabbitMQ延时队列插件实录

先说个背景。我最近接手了一个2017年前后上线的老服务,技术栈固定在Spring Boot 1.4.7.RELEASE上,消息队列用的是RabbitMQ。业务上有个硬需求:工单提交后30分钟内无人处理要触发升级提醒,下单后15分钟未支付要自动关闭。这种“过一段时间再干活”的需求,就是典型的延时消息场景。网上关于Spring Boot 2.x、3.x接RabbitMQ延时队列的教程一抓一大把,但Spring Boot 1.4这个老版本加上延时队列插件的完整实践却很少。我这次把插件装好、代码调通、还顺手解决了几个挺隐蔽的坑,所以专门写一篇,给还在维护老项目、或者说被安排“在1.4上搞延时队列”的同行做个参考。

这篇内容我会按这样的顺序来:先分析为什么在2026年还要守着1.4做延时队列、插件方案和死信队列方案怎么选;然后讲版本匹配和插件安装;再给出Spring Boot 1.4连接RabbitMQ的完整配置和延时消息的生产消费代码;最后把我实际踩过的坑——从RabbitMQ启动失败、连接时报406/404、到消息不延时和丢失——全部列出来,附上排查思路。内容偏向实操,直接照着抄基本能跑通。

1. 先讲清楚:为什么还在Spring Boot 1.4上搞延时队列

1.1 这需求是真需求:订单超时、工单升级都靠它

延时队列解决的问题其实很朴素:不是“立即让消费者收到消息”,而是“在指定时间之后才让消息变得可见、可被消费”。听起来简单,真正落地时方案不少,不同方案之间差异极大。

订单超时未支付自动关闭就是一个典型场景。用户下单后,订单系统产生一条“15分钟后关闭订单”的消息,这条消息进入延时队列,15分钟之内它不会出现在消费者视野里,一旦到期,RabbitMQ自动把它投递给真正处理关闭订单的消费者,业务代码把订单状态置为已取消即可。工单升级提醒同理,30分钟后还没被处理,消息被投递出来,系统发送站内信或短信。

我遇到的这个老服务,之前用的方案很粗暴:后台一个定时线程池,每隔30秒扫一遍数据库,把超时的记录捞出来处理。这在数据量小的时候没问题,但一旦单量上来,每次全表扫描的性能开销、还有扫描间隔带来的时间误差,都是硬伤。换用RabbitMQ延时队列之后,业务侧只需要发送一条带延迟时间的消息,到期自动触发,误差可以做到秒级甚至毫秒级,数据库压力也小了很多。

1.2 三条技术路线,最后为什么选了插件

做延时消息,主流的技术路线有三条。我先对比一下,再说为什么最终选择了插件方案。

第一条是用RabbitMQ自带的死信队列(DLX)+ 消息TTL。思路是:先声明一个普通的队列,不挂消费者,发送消息时给消息设置过期时间(expiration),队列里消息到期后,会被RabbitMQ投递给绑定的死信交换机,再由死信交换机路由到真正消费的队列。这种做法不需要装任何插件,纯RabbitMQ自带功能。但它有个明显痛点:同一队列里的消息过期时间通常是一致的,如果你需要不同的延迟时间,就要为每个延迟级别单独建一个队列和一套绑定,代码量和运维成本随延迟级别数量线性增长。而且RabbitMQ对队列中的过期消息是“队头检查”模式,如果队头消息延迟很长,后面的短延迟消息会被堵住,延迟精度完全取决于队头判断,这是个很容易踩的坑。

第二条是用Spring Boot里的@Scheduled定时任务加数据库轮询。简单直接,不依赖中间件,但轮询间隔决定了延迟精度,且并发量上来以后数据库压力大,逻辑上也需要维护任务状态。

第三条就是标题里的方案:RabbitMQ官方延时队列插件rabbitmq_delayed_message_exchange。它通过给消息加一个“x-delay”头,指定延迟毫秒数,消息进入x-delayed-message类型的交换机后,不会被立即路由,而是等到延迟时间到了才投递给绑定的队列。对比死信队列方案,最爽的一点是每条消息可以单独指定延迟时间,不需要为不同延迟建多个队列;对比数据库轮询,精度高且不占业务库的查询压力。

我最终选了插件方案,还有一个现实原因:这个老项目的部署环境里,RabbitMQ是独立安装的,运维同事可以很方便地添加插件文件,不需要改业务代码,也不需要动一下代码就重新发布整个Spring Boot服务。插件方案对业务代码的侵入也最小,生产者和消费者几乎不用改原有逻辑,只增加一个延时交换机即可。

提示:延时插件方案并非完全没有缺点。rabbitmq_delayed_message_exchange插件在RabbitMQ内部会把未到期消息暂存起来,占用一定的内存和磁盘资源。延迟消息量很大、延迟时间动不动好几小时甚至几天的时候,建议评估一下资源占用,不要无脑上插件。

2. 版本匹配和插件安装,这里最容易翻车

2.1 Spring Boot 1.4、Spring AMQP、RabbitMQ、插件之间的版本关系

先把版本关系捋清楚,这是整个项目里最大的坑。Spring Boot 1.4.x引入spring-boot-starter-amqp时,管理的是Spring AMQP 1.6.x系列。Spring AMQP是Spring对RabbitMQ客户端的高级封装,RabbitTemplate、@RabbitListener、声明队列交换机绑定全靠它。Spring AMQP 1.6这个版本我没有做任何升级,保持Spring Boot父工程指定的默认版本就够用,因为它已经支持CustomExchange,足够声明x-delayed-message类型交换机。

RabbitMQ服务端的版本则需要和延时插件版本匹配。我这样说吧:插件名称是rabbitmq_delayed_message_exchange,它是一个独立的.ez文件,需要手动下载并放入RabbitMQ的plugins目录。插件的版本号要和RabbitMQ服务端的大版本一致,比如RabbitMQ服务端是3.8.x,就找3.8.x的插件release;服务端是3.7.x,就找3.7.x版本。如果版本对不上,启用插件时大概率报错,或者虽然能启用但运行期行为异常。

做一个速查表,方便你对着查:

RabbitMQ服务端插件版本方向常用Erlang版本
3.6.x3.6.x老版本插件Erlang 18/19
3.7.x3.7.x插件Erlang 20/21
3.8.x3.8.x插件Erlang 21及以上
后续新版本优先官方release页面给出的对应版本Erlang保持匹配

表格只是给出匹配原则,别把版本号当唯一真理。另外,Erlang版本和RabbitMQ的匹配关系也很容易踩坑,RabbitMQ官方文档给了一个Elixir/Erlang兼容矩阵,装服务端之前最好查一下你手头的Erlang环境是否被支持,否则RabbitMQ服务可能反复启动失败。

2.2 装插件:Windows/Linux双系统实录

插件安装本身不复杂,关键是把文件放对位置,并且用对启用命令。我两台环境都装过,分别说。

Linux环境里,找到RabbitMQ的安装目录,比如/usr/lib/rabbitmq/lib/rabbitmq_server-3.8.x/plugins/。把下载好的rabbitmq_delayed_message_exchange-xxx.ez文件拷贝到这个目录下,然后执行:

rabbitmq-plugins enable rabbitmq_delayed_message_exchange

执行完看到类似“The following plugins have been configured: rabbitmq_delayed_message_exchange”就算成功了。如果没有权限,会报错,提示检查目录可写性,用root权限执行或者调整目录属主即可。

Windows环境里,RabbitMQ通常安装在C:\Program Files\RabbitMQ Server\rabbitmq_server-3.8.x\plugins目录。把插件文件放进去之后,用管理员身份打开命令行,执行:

rabbitmq-plugins.bat enable rabbitmq_delayed_message_exchange

这里有一个常见坑:Windows下RabbitMQ服务是作为Windows服务运行的,插件目录如果有权限限制,服务进程读不到插件文件,你会看到服务起来了但插件没生效。解决办法是确认插件文件确实在plugins目录下,并且启动RabbitMQ服务的Windows用户对该目录有读取权限。实在不行,先在命令行里用rabbitmq-plugins.bat list查看插件是否被识别,再确认服务是否重启。

我自己的经验是:不管Windows还是Linux,装完插件一定手动重启一次RabbitMQ服务,或者执行rabbitmq-diagnostics的命令刷新插件状态,别太相信“enable成功就是最终状态”。

2.3 装完怎么确认插件真的生效了

启用插件后,用下面命令查看插件列表:

rabbitmq-plugins list

输出中应包含:

rabbitmq_delayed_message_exchange

另外一个更直观的验证方式是打开RabbitMQ的Web管理界面(默认端口15672),进入“Exchanges”页面,点击“Add a new exchange”,在“Type”下拉框里如果出现了x-delayed-message类型,就说明插件已经生效。这一步是很多教程没提到的,但却是最快确认插件可用的方式。

注意:如果管理界面里看不到x-delayed-message,即使rabbitmq-plugins list显示已启用,也建议重启RabbitMQ服务再刷新页面。我遇到过一次插件列表显示正常但管理界面一直不出现类型选项的情况,重启后解决,原因是RabbitMQ的Web管理组件没有重新加载插件类型注册表。

3. Spring Boot 1.4连接RabbitMQ的基础配置

3.1 Maven依赖和yml配置,老版本别乱升级

Spring Boot 1.4里连接RabbitMQ,首先要在pom.xml中加入依赖:

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.7.RELEASE</version> <relativePath/> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> </dependencies>

这里必须说一句:别手贱去升级spring-boot-starter-amqp的版本,让它跟着1.4.7.RELEASE父工程走就行。Spring Boot 1.4自带的Spring AMQP 1.6.x对RabbitTemplate、@RabbitListener、Binding这些核心API的支持已经足够,强行升级到Spring AMQP 2.x反而容易引发API不兼容,比如ConnectionFactory的创建方式、消息转换器的默认行为都会变。

然后是application.yml。Spring Boot 1.4的RabbitMQ自动配置读取的是spring.rabbitmq前缀:

spring: rabbitmq: host: 127.0.0.1 port: 5672 username: guest password: guest virtual-host: / publisher-confirms: true

publisher-confirms这个配置项对应的是RabbitTemplate的confirm功能。1.4版本里是true/false开关,到了Spring Boot 2.x才改成publisher-confirm-type: correlated。还有publisherReturns,对应returnCallback,可以用于消息投递失败的回调。我在生产环境里会把这两个都打开,因为延时消息一旦发送失败且没有回调,业务上很难感知,后面排查消息丢失时就会非常被动。

3.2 配置类里的几个细节:连接工厂、RabbitTemplate

Spring Boot 1.4的自动配置会自动创建一个CachingConnectionFactory和一个RabbitTemplate,但有些细节需要手动调整。我建议显式定义配置类,至少把RabbitTemplate的mandatory开关和消息转换器确认好。

@Configuration @EnableRabbit public class RabbitConfig { @Bean public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { RabbitTemplate template = new RabbitTemplate(connectionFactory); template.setMandatory(true); return template; } }

setMandatory(true)的作用是:如果消息无法路由到任何队列,RabbitMQ会把消息返回给生产者,触发ReturnCallback。这在你排查“消息去哪里了”的时候非常有用。1.4的RabbitTemplate默认强制消息只能路由成功,不设置mandatory的话,路由失败的消息会被静默丢弃。

消息转换器也建议看一下。默认情况下convertAndSend发送一个String对象,走的是SimpleMessageConverter,消息体是字节数组,消费者收到的也是String,没问题。但如果要发送JSON对象,建议统一配置Jackson2JsonMessageConverter:

@Bean public Jackson2JsonMessageConverter jackson2JsonMessageConverter() { return new Jackson2JsonMessageConverter(); } @Bean public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { RabbitTemplate template = new RabbitTemplate(connectionFactory); template.setMandatory(true); template.setMessageConverter(jackson2JsonMessageConverter()); return template; }

这里有一个细节:生产端和消费端的消息转换器要一致,否则消费者反序列化时容易报类型错误、或者拿到一堆乱码字节。老项目里如果生产端是另一个服务,两边都要同步确认Converter配置。

4. 让延时插件真正跑起来:交换机、生产者、消费者

4.1 用CustomExchange声明x-delayed-message交换机

核心来了。延时插件的工作机制是:消息投递到一个类型为x-delayed-message的交换机,这个交换机不会立刻把消息路由到队列,而是把消息暂时保存起来,等延迟时间到了,才执行路由逻辑,把消息投给绑定的队列。

因此,声明交换机的时候,type必须写x-delayed-message,并且要额外设置一个参数x-delayed-type,指定“延迟结束之后按什么类型路由”。这个参数是插件规定的,用来模拟direct、topic、fanout等路由行为。我把延时交换机设成direct类型,配合固定的routingKey,简单直接。

@Configuration public class DelayRabbitConfig { public static final String DELAY_EXCHANGE = "delay.exchange"; public static final String DELAY_QUEUE = "delay.queue"; public static final String DELAY_ROUTING_KEY = "delay.key"; @Bean public CustomExchange delayExchange() { Map<String, Object> args = new HashMap<>(); args.put("x-delayed-type", "direct"); return new CustomExchange(DELAY_EXCHANGE, "x-delayed-message", true, false, args); } @Bean public Queue delayQueue() { return new Queue(DELAY_QUEUE, true); } @Bean public Binding delayBinding() { return new Binding(DELAY_QUEUE, Binding.DestinationType.QUEUE, DELAY_EXCHANGE, DELAY_ROUTING_KEY, null); } }

CustomExchange是Spring AMQP里用来声明非标准类型交换机的类,RabbitMQ内置direct、topic、fanout等类型各有专门的类,但x-delayed-message是插件扩展类型,只有CustomExchange能表达。声明参数里,构造方法依次是交换机名称、类型、durable、autoDelete、arguments。durable必须为true,表示交换机重启后还在,arguments里的x-delayed-type必须保留。第三个参数args为null也是允许的,对应绑定不需要额外参数。

这里有个非常容易踩的坑:如果之前你已经用同一个交换机名声明过一个普通direct类型交换机,再启动项目声明x-delayed-message交换机,RabbitMQ会直接拒绝,报406 PRECONDITION_FAILED。解决办法是到管理界面手动删除旧交换机,或者换个新的交换机名称。队列和交换机一样,同名不同类型/参数冲突也会报406。

4.2 生产者发送延时消息的完整写法

生产者的核心逻辑很简单:发送消息到延时交换机,消息带上x-delay头,值是延迟的毫秒数。Spring AMQP中,MessageProperties负责承载消息头。我建议直接用setHeader("x-delay", 延迟毫秒数),这是插件约定的原生写法,不依赖Spring AMQP对delay属性是否有特殊处理,最保险。

@Component public class DelayMessageProducer { @Autowired private RabbitTemplate rabbitTemplate; public void sendDelayMessage(String message, long delayMillis) { MessagePostProcessor messagePostProcessor = new MessagePostProcessor() { @Override public Message postProcessMessage(Message message) throws AmqpException { message.getMessageProperties().setHeader("x-delay", (int) delayMillis); return message; } }; rabbitTemplate.convertAndSend( DelayRabbitConfig.DELAY_EXCHANGE, DelayRabbitConfig.DELAY_ROUTING_KEY, message, messagePostProcessor ); } }

调用时:

delayMessageProducer.sendDelayMessage("订单未支付,请关闭", 15 * 60 * 1000L);

这里我有几个值得说的点。第一,x-delay头的单位是毫秒,而且插件要求它是一个整数。60000表示1分钟,15分钟就是900000,不要写成秒,这是最常见的低级错误。第二,int的位数上限决定了延迟最长时间大约在24.8天左右(Integer.MAX_VALUE毫秒),如果业务上要延迟一个月甚至更久,这个方案就不合适了,建议换数据库调度。第三,MessagePostProcessor里不要顺手调用message.getMessageProperties().setDelay(...)再同时setHeader("x-delay", ...),两个机制混用容易造成头被覆盖或者类型不一致,选一种就行。

还有一点经验之谈:发送延时消息之后,最好记一条业务日志,带上消息ID、交换机、routingKey、延迟时间。一旦后续消息丢失或者延迟不准,你至少有线索可以查。RabbitTemplate的convertAndSend方法如果你不传Message对象、传的是String对象,默认生成的messageId是null,排查时很痛苦,可以在MessagePostProcessor里给messageProperties.setMessageId(UUID.randomUUID().toString())。

4.3 消费者侧需要注意的队列绑定与ACK行为

消费者端不需要感知x-delay头。消息到期后由延时交换机自动投喂到队列,消费者只是一条普通队列的消费者。一个最简单的@RabbitListener写法如下:

@Component public class DelayMessageConsumer { @RabbitListener(queues = DelayRabbitConfig.DELAY_QUEUE) public void process(String message) { // 到期之后这里才会被触发 System.out.println("收到延时消息: " + message); } }

但真实生产环境里,我建议使用手动ACK,并且在业务处理成功后才确认消息。Spring Boot 1.4里可以手动定义SimpleRabbitListenerContainerFactory,设置AcknowledgeMode.MANUAL:

@Bean public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) { SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); factory.setConnectionFactory(connectionFactory); factory.setAcknowledgeMode(AcknowledgeMode.MANUAL); return factory; }

消费者方法接收Channel和Message参数,自己手动确认:

@Component public class DelayMessageConsumer { @RabbitListener(queues = DelayRabbitConfig.DELAY_QUEUE) public void process(Message message, Channel channel) throws Exception { try { String body = new String(message.getBody(), "UTF-8"); // 在这里处理业务逻辑 channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } catch (Exception e) { channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); } } }

使用手动ACK,重启服务时未确认的消息会重新投递,不会因为服务恰好宕机在消息到期节点就丢掉业务。延时消息通常是“业务需要确保最终执行”的,比如关闭订单、升级工单,丢一条的代价都不小。所以在Spring Boot 1.4里手动ACK这条别省。

另一个注意点是消费者的并发度。延时消息到期后往往会有一个“集中触发”的尖峰,因为很多业务都习惯把延迟时间设成整点、整分钟。如果队列积压了很多同时到期的消息,但消费者并发只有1,消费速度会跟不上。可以在容器工厂里设置factory.setConcurrentConsumers(5)和factory.setMaxConcurrentConsumers(15),给消费者留出弹性扩展空间。

5. 实操中遇到的坑:从启动失败到消息不延时

5.1 RabbitMQ服务启动失败的几类原因

先说服务启动失败,这类问题卡住的话后面全白搭。

第一类:端口冲突。RabbitMQ默认5672,如果本机已经有一个进程占用了5672,服务起不来。Windows下特别常见,排查命令是netstat -ano | findstr 5672,找出占用进程PID,再通过任务管理器结束或者换端口。也可以修改RabbitMQ配置文件里的TCP监听端口,但改动配置会让其他依赖方也一起变,能不动就别动。

第二类:Erlang版本和RabbitMQ不匹配。我见过一台服务器上RabbitMQ 3.8.x配了很低版本的Erlang,启动时直接崩,日志里一大段Erlang异常栈。排查办法不是看进程有没有起来,而是执行rabbitmq-diagnostics status看版本信息,确认Erlang release是否在RabbitMQ支持范围内。

第三类:主机名问题。RabbitMQ服务依赖系统主机名解析,某些场景下主机名解析失败会导致EPMD启动失败。Linux下可以通过/etc/hosts把主机名映射到127.0.0.1临时解决。

第四类:Windows服务权限不足。以Windows服务方式安装RabbitMQ后,插件目录、数据目录如果权限配置不对,服务启动报错。打开Windows事件查看器看到RabbitMQ相关错误时,优先看是不是“Access Denied”。

5.2 连接RabbitMQ时报NOT_FOUND/406/ACCESS_REFUSED

启动Spring Boot服务时,你可能会遇到这样一串报错:

cause: clean channel shutdown; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'delay.exchange' in vhost '/', class-id=60, method-id=40)

404 NOT_FOUND通常是交换机或队列没有被声明成功。最常见的场景是:你在生产端代码里发送消息到delay.exchange,但应用启动时并没有执行配置类里的delayExchange()方法,也就是配置类没被Spring扫描到。检查一下启动类的包扫描路径,确认RabbitConfig所在的包在扫描范围内。

另外还有一种404:交换机的名字或者vhost和后端不匹配。如果配置里virtual-host写的是/,消息实际也是在默认vhost /下,对不上会报错。RabbitMQ对vhost敏感,生产环境的vhost经常是独立创建的,比如/shop,配置要跟实际一致。

406 PRECONDITION_FAILED是另一个高频报错。前面提到过,同名交换机类型不同会报406;同名队列参数不同也会报406。比如之前队列声明时是durable=false,现在改成durable=true,RabbitMQ拒绝修改已存在队列,直接406。排错思路是去管理界面看这个名称的队列/交换机是否已经存在,存在就删除掉再重新启动项目。

还有ACCESS_REFUSED:默认情况下guest用户只能通过localhost访问RabbitMQ,如果Spring Boot应用和RabbitMQ不在同一台机器上,用guest连一定失败。这时候要创建一个专用账号并赋权:

rabbitmqctl add_user mquser mqpass rabbitmqctl set_permissions -p / mquser ".*" ".*" ".*"

然后在application.yml里把username/password改成新账号,问题立刻消失。这是远程连接RabbitMQ时最容易踩的安全限制,别在默认guest上死磕。

5.3 消息不延时、丢失、堆压怎么办

延迟时间没生效,消息发下去立刻被消费掉了。出现这种问题,我通常按下面顺序排查:

先看消息头。到管理界面的“Queues”页面,选中目标队列,查看消息的Headers。如果看到x-delay不在里面,说明生产端MessagePostProcessor没有生效。一个比较隐蔽的问题是:如果你传的是String消息,但RabbitTemplate被配置了Jackson2JsonMessageConverter,convertAndSend重载方法可能没有按预期应用PostProcessor。检查方法是在MessagePostProcessor里加个日志,把message.getMessageProperties().getHeaders()打印出来。

再看路由键和交换机类型。延时交换机必须确实被声明成x-delayed-message类型,routingKey要能匹配to指定的key。如果你不小心把消息发到了另一个同名的普通交换机,消息自然延时不了。

还有一种情况:x-delay头被设置成了字符串类型。插件要求x-delay是整数,有些JSON配置里写成了"900000",类型不严格匹配时会投递异常。用MessageProperties.setHeader时,务必传Integer,我前面的代码里特意强转成int,就是为了避开这个坑。

消息丢失问题,首先要查mandatory有没有开、ReturnCallback有没有写。打开mandatory之后,路由失败的消息会触发ReturnCallback,你可以打日志。代码可以这样注册callback:

rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() { @Override public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) { System.out.println("消息路由失败: " + replyCode + " " + replyText); } });

消息堆积问题,通常是消费者处理太慢。除了调高并发度之外,还要看消费者里有没有做耗时的远程调用。如果是调外部接口导致耗时,建议把外部调用改成异步,或者增加重试队列,避免把RabbitMQ的消费线程拖死。

提示:如果消息延迟时间特别长,比如几小时甚至几天,插件的调度误差会被拉大。rabbitmq_delayed_message_exchange的实现在内部使用定时器扫描,延迟消息越多、延迟时间越长,实际触发时间可能比设定时间晚几十秒甚至更久。对时间精度要求很高(秒级)的场景,建议把延迟控制在分钟到小时级别内,超长延迟改用数据库调度。这个特性限制在官方文档里有说明,但很多教程不会提醒。

6. 最后说点维护老项目的心得

Spring Boot 1.4很老,但存量系统不会因为新版本出现就自动消失。我这次做完延时队列后有个体会:老版本并不等于不能做新功能,关键在于把版本边界摸清楚,不强行升级,也不排斥新方案。RabbitMQ延时插件从RabbitMQ 3.6.x时代就有了,Spring AMQP 1.6的CustomExchange能稳定支撑,甚至不需要改Spring Boot版本,这点可能很多人没意识到。

在我实操的过程中,最大的效率来源反而是先把问题定义清楚:我需要的是“每条消息独立延迟”,不是“队列级别延迟”。就这一个判断,直接决定了我放弃死信队列方案。如果当时只想着“用RabbitMQ实现延迟”,大概率会在TTL+DLX的各种坑里打转,绕一圈又回来。

最后分享两个我自己的习惯。第一个,测试延时消息时永远从最短时间开始验证,比如先发一条延迟5秒的消息,确认能消费后,再逐步拉长到1分钟、30分钟。你要是直接发一条30分钟的延时消息,等它触发时可能已经忘了自己测的是哪一条。第二个,延时队列上线后一定要加监控,至少监控队列消息堆积数和消息年龄。RabbitMQ管理界面能看到“message count”和“unacked”,消息堆积持续上涨就是消费端出问题的信号,不能等用户反馈才发现。

如果你也在维护一个老版本Spring Boot项目,正为延时消息头疼,按照这篇文章把插件装上、把交换机声明成x-delayed-message、发送时带上x-delay头,基本上就能跑通。剩下的坑,无非是版本、权限、类型冲突这几类,对照第五节逐一排查就行。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 3:06:25

Python实现3D-CT肺结节检测:双阶段架构与全流程解析

简介&#xff1a;这是一份以Python语言实现的3D-CT影像肺结节检测算法项目&#xff0c;整合源码、数据集与项目说明&#xff0c;面向计算机、通信、人工智能、自动化等相关专业的学生、教师和从业者&#xff0c;适合用作毕业设计、期末大作业与课程设计的完整参考。项目源于高分…

作者头像 李华
网站建设 2026/10/3 3:05:53

SSM+Vue农资管理系统开发实战:从设计到部署

毕业设计选了个农资管理系统&#xff0c;用SSM搭后端、Vue写前端&#xff0c;听起来挺常规的&#xff0c;但真动手做起来&#xff0c;从数据库设计到前后端联调&#xff0c;再到最后打包部署、写论文&#xff0c;每一步都有不少坑。这篇文章我就把“基于SSMVUE的益农农资管理系…

作者头像 李华
网站建设 2026/10/3 3:05:39

Python 3D CT肺结节检测项目拆解:数据预处理到训练推理全流程

简介&#xff1a;这是一份面向计算机、人工智能、医学影像等相关专业学生与从业者的Python 3D-CT肺结节检测项目源码&#xff0c;基于深度学习覆盖数据预处理、候选结节分割、分类识别与预测输出的完整链路&#xff0c;适合作为毕业设计、期末大作业或进阶实战参考。压缩包共53…

作者头像 李华
网站建设 2026/10/3 3:05:08

SpringBoot2+Vue3+MyBatis-Plus前后端分离项目实战:流浪动物救助平台设计

又是一年毕设季&#xff0c;后台收到不少读者问同一个问题&#xff1a;想找一个业务真实、技术栈主流、前后端分离、还能直接跑起来的Java Web项目做参考&#xff0c;翻遍Gitee和GitHub&#xff0c;要么是烂大街的图书管理系统&#xff0c;要么是只有前端没有后端的半成品。流浪…

作者头像 李华
网站建设 2026/10/3 3:05:02

Agent开发工程实践:从模型能力到生产落地的避坑指南

DeepSeek-V 一发布&#xff0c;我的朋友圈基本被刷屏了。但比起"推理能力又提升了多少"这种常规讨论&#xff0c;我更关注的是另一件事&#xff1a;作为大模型开发工程师&#xff0c;我明显感觉到身边讨论 Agent 的人越来越多了。并不是那种"AI 会取代人类"…

作者头像 李华
网站建设 2026/10/3 3:01:29

从Postman到Apifox:API一站式协作与自动化测试实战指南

说实话&#xff0c;第一次打开 Apifox&#xff0c;我的第一反应是&#xff1a;"这不就是个换了皮肤的 Postman 吗&#xff1f;" 但真正用它写完一个项目的接口文档、Mock 数据、自动化测试之后&#xff0c;我承认当初的判断太草率了。这玩意儿本质上不是一个"调…

作者头像 李华