家人们谁懂啊!昨天在测试环境对接 RocketMQ 5.0 延迟消息,给我整了个大活 ------ 本地 5.0/5.3.2 版本跑得飞起,测试环境一发送就报CODE:14,屏幕上 "service not available" 几个字看得我脑壳发麻。和运维折腾了一下午,最后发现凶手居然运维在配置文件里的一个空格!这波踩坑经历,简直是 "低级错误教做人" 现场。
一、现场:测试环境的 "迷之报错",本地却啥事没有
先上灵魂暴击的报错日志(就是我当时甩在群里的原版,一字没改):
less
2025-12-03 17:19:56.172 ERROR 27112 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.apache.rocketmq.client.exception.MQClientException: Send [3] times, still failed, cost [531]ms, Topic: test-delay-msg, BrokersSent: [broker-a, broker-a, broker-a]
See http://rocketmq.apache.org/docs/faq/ for further details.] with root cause
org.apache.rocketmq.client.exception.MQBrokerException: CODE: 14 DESC: service not available now. It may be caused by one of the following reasons: the broker's disk is full [CL: 0.53 CQ: 0.53 INDEX: 0.53], messages are put to the slave, message store has been shut down, etc. BROKER: 172.28.200.4:10911
For more information, please visit the url, http://rocketmq.apache.org/docs/faq/
at org.apache.rocketmq.client.impl.MQClientAPIImpl.processSendResponse(MQClientAPIImpl.java:779) ~[rocketmq-client-5.0.0.jar:5.0.0]
at org.apache.rocketmq.client.impl.MQClientAPIImpl.sendMessageSync(MQClientAPIImpl.java:619) ~[rocketmq-client-5.0.0.jar:5.0.0]
at org.apache.rocketmq.client.impl.MQClientAPIImpl.sendMessage(MQClientAPIImpl.java:601) ~[rocketmq-client-5.0.0.jar:5.0.0]
at org.apache.rocketmq.client.impl.MQClientAPIImpl.sendMessage(MQClientAPIImpl.java:545) ~[rocketmq-client-5.0.0.jar:5.0.0]
at org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl.sendKernelImpl(DefaultMQProducerImpl.java:907) ~[rocketmq-client-5.0.0.jar:5.0.0]
at org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl.sendDefaultImpl(DefaultMQProducerImpl.java:643) ~[rocketmq-client-5.0.0.jar:5.0.0]
at org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl.send(DefaultMQProducerImpl.java:1426) ~[rocketmq-client-5.0.0.jar:5.0.0]
at org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl.send(DefaultMQProducerImpl.java:1369) ~[rocketmq-client-5.0.0.jar:5.0.0]
群里运维求助的时候我都懵了:
- 本地搭的 RocketMQ 5.0、5.3.2,发
test-delay-msg这个 Topic 的延迟消息,秒发送、秒投递,10 秒延迟精准得很; - 切到测试环境,代码一行没改、配置项也都配了,重启 Broker 三次,还是连番报 CODE:14,日志里 "service not available" 快把我眼睛晃瞎了;
- 甚至怀疑过是不是测试环境的 RocketMQ 不是官方版本,查了安装包,妥妥的官网下载的 5.0 版本... 当时我盯着那行
disk is full [CL: 0.53 CQ: 0.53 INDEX: 0.53]还傻呵呵去查磁盘 ------ 使用率才 53%,离满格差十万八千里,这报错纯纯 "误导人"!
二、排查:从 "怀疑人生" 到揪出 "空格刺客"
第一波排查:把能试的都试了,全白搭
我跟这个 BUG 死磕的过程,主打一个 "病急乱投医":
- 查网络:telnet 172.28.200.4:10911,通的;ping Namesrv,延迟正常;
- 查权限:Topic
test-delay-msg的读写权限全开,Producer Group 也没配错; - 查延迟等级:用的默认 3 级(10 秒),没超 1-18 的范围;
- 重启大法:Broker、Namesrv、应用服务轮着重启,报错纹丝不动;
- 对比版本:本地和测试环境都是 rocketmq-client-5.0.0.jar,版本完全一致。
当时我都快怀疑是不是测试环境的 Broker 被 "下咒" 了,甚至翻了 RocketMQ 官方 FAQ,结果 FAQ 里说的 "磁盘满、从节点存储、消息存储关闭",我这边一个都没中!
第二波排查:逐字符对比配置文件,发现 "隐形凶手"
绝望中我把本地和测试环境的broker.conf拉到一起逐行对比 ------ 就差拿放大镜看了,终于!在测试环境的配置行里,发现了那个藏在数字后面的空格(就是我之前提的 "后面有空格" 的坑):
就这一个不起眼的空格,直接把 RocketMQ 5.0 的 Broker 干废了!
三、破案:为啥一个空格能搞崩延迟消息?
RocketMQ 5.0 的延迟消息全靠 "定时轮(timerWheel)" 这个核心功能撑着 ------ 相当于 Broker 里有个精准的 "闹钟",到点就把延迟消息推给消费者。而这个 "闹钟" 能不能启动,全看timerPrecisionMs这些配置项的解析结果:
- RocketMQ 5.0 对配置解析的容错性比老版本低了 N 个档次,等号后多一个空格,它就把
1000(带空格)当成 "非法数字"; - 非法配置直接导致定时轮服务初始化失败,延迟消息模块直接躺平,返回 "service not available"(CODE:14);
- 更坑的是,这种配置错误不会在 Broker 启动日志里报错,只会在发延迟消息时炸锅,纯纯的 "暗箭伤人"!
四、救场:三步送走这个 "空格刺客"
第一步:给配置文件 "刮胡子"------ 删光多余空格
把测试环境broker.conf里所有配置项的多余空格全清掉,保证key=value是 "无缝衔接" 的状态:
ini
# 修正后的正确配置
timerWheelEnable = true
timerPrecisionMs=1000
timerRollWindowSlot=86400
别嫌麻烦,每一行都要检查!尤其是复制粘贴来的配置,很容易带空格。
第二步:重启 Broker,验证配置是否生效
bash
# 先停掉摆烂的Broker
sh mqshutdown broker
# 用干净的配置重启
nohup sh mqbroker -c ../conf/broker.conf &
# 检查定时轮服务是否正常启动
sh mqadmin getBrokerConfig -n 127.0.0.1:9876 -b 172.28.200.4:10911 | grep "timerWheelEnable"
只要输出timerWheelEnable=true,说明 "闹钟" 终于正常工作了。
第三步:测试延迟消息,终于扬眉吐气
重新写了行测试代码,手都抖着点了运行:
ini
// 测试延迟消息发送
DefaultMQProducer producer = new DefaultMQProducer("test-producer-group");
producer.setNamesrvAddr("172.28.200.4:9876");
producer.start();
Message msg = new Message("test-delay-msg", "终于能发延迟消息了!".getBytes());
msg.setDelayTimeLevel(3); // 10秒延迟
SendResult result = producer.send(msg);
System.out.println("发送成功!消息ID:" + result.getMsgId());
producer.shutdown();
10 秒后,消费者日志弹出 "终于能发延迟消息了!"------ 那一刻,我直接在工位上长舒一口气!
五、血的教训:RocketMQ 5.0 延迟消息的 "细节暗杀"
- 配置文件别瞎加空格:5.0 版本对配置格式是 "处女座级别的严格",等号前后、行尾的空格都可能触发隐藏 BUG;
- 延迟消息 = 定时轮 + 配置 :发延迟消息前,先查
timerWheelEnable是不是 true,别等报错了才回头; - 本地能跑≠测试环境能跑:环境差异要 "逐字符对比",尤其是配置文件,别嫌麻烦,早对比早省心;
- CODE:14 别只看磁盘:这个报错的误导性极强,磁盘满只是其中一个原因,配置错误才是高频坑!
- 最后友情附赠个小技巧:把配置文件复制到记事本里开 "显示所有字符",空格、换行这些 "隐形刺客" 立马现形!