🧑 博主简介:CSDN博客专家,「历代文学网」(PC端可以访问:https://lidaiwenxue.com/#/?__c=1000,移动端可关注公众号 “ 心海云图 ” 微信小程序搜索“历代文学”)总架构师,首席架构师,也是联合创始人!
16年工作经验,精通Java编程,高并发设计,分布式系统架构设计,Springboot和微服务,熟悉Linux,ESXI虚拟化以及云原生Docker和K8s,热衷于探索科技的边界,并将理论知识转化为实际应用。保持对新技术的好奇心,乐于分享所学,希望通过我的实践经历和见解,启发他人的创新思维。在这里,我希望能与志同道合的朋友交流探讨,共同进步,一起在技术的世界里不断学习成长。
🤝商务合作:请搜索或扫码关注微信公众号 “心海云图”


RocketMQ 5.5.0 Docker 部署踩坑实录
前言
最近在将 RocketMQ 从 5.3.3 迁移到 5.5.0 的过程中,选择了官方 apache/rocketmq:5.5.0 镜像,并搭建了一套两主两从的 Docker 集群。本以为会是一个简单的版本升级,没想到一路踩坑无数。
本文将这些坑点逐一记录下来,希望能帮助到正在或即将部署 RocketMQ 5.5.0 的你。
环境说明
- 操作系统:CentOS 7 / Rocky Linux 8
- 容器运行时:Docker 20.10+
- 镜像版本:
apache/rocketmq:5.5.0(实际使用registry.cn-hangzhou.aliyuncs.com/sinhy/rocketmq:5.5.0,基于官方镜像重命名) - 集群架构:两主两从(2m-2s-sync),共 4 个 Broker 节点 + 2 个 NameServer 节点
目录
- 镜像内 UID 是 3000 而非 1000
- JVM 环境变量名称错了
- 直接内存为何要设大
- JDK 8 不支持 --add-exports
- SELinux 阻止容器写入挂载目录
- logs/rocketmqlogs 子目录需提前创建
- brokerName 和 brokerId 配置要唯一
- –network host 模式下 -p 端口映射失效
- store 目录为空(正常现象)
- 总结:一键部署脚本
一、镜像内 UID 是 3000 而非 1000
现象
容器启动后,日志无法写入挂载的宿主机目录。检查目录权限后,发现已经执行了 chown -R 1000:1000,但依然报错 Permission denied。
原因
官方 apache/rocketmq:5.5.0 镜像内部,rocketmq 用户的 UID 是 3000,而非常见的 1000。
# 进入容器验证
docker exec -it broker-a-10911 id
# 输出: uid=3000(rocketmq) gid=3000(rocketmq)
很多周边项目(如 rocketmq-operator)使用的是 UID 1000,但官方核心镜像的标准是 3000。
解决方案
挂载目录的所有者必须设置为 3000:
sudo chown -R 3000:3000 /data/docker-containers/rocketmq/broker
sudo chmod -R 755 /data/docker-containers/rocketmq/broker
教训:永远不要假设容器内用户的 UID,直接用 docker exec 进去确认。
二、JVM 环境变量名称错了
现象
明明在 docker run 中通过 -e JAVA_OPTS="..." 设置了 JVM 参数,但 ps aux | grep java 看到的值仍是脚本默认值,导致 OutOfMemoryError: Direct buffer memory 不断出现。
原因
RocketMQ 5.5.0 的启动脚本 runbroker.sh / runserver.sh 中,读取的是 JAVA_OPT_EXT 环境变量,而不是 JAVA_OPTS 或 ROCKETMQ_OPTS。
脚本中关键行:
JAVA_OPT="${JAVA_OPT} ${JAVA_OPT_EXT}"
解决方案
使用正确的环境变量名:
-e "JAVA_OPT_EXT=-Xss512k -Xms512m -Xmx2048m -XX:MaxDirectMemorySize=6g -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=256m"
教训:不同版本的 RocketMQ 镜像可能使用不同的环境变量名,务必查看 runbroker.sh 源码确认。
三、直接内存为何要设大
现象
即使设置了 JAVA_OPT_EXT,Broker 仍报错:
java.lang.OutOfMemoryError: Direct buffer memory
at org.apache.rocketmq.store.TransientStorePool.init(TransientStorePool.java:50)
原因
RocketMQ Broker 默认开启了 TransientStorePool(堆外内存池)。该组件会初始化一组 DirectByteBuffer 作为循环复用的缓冲区,其数量由 transientStorePoolSize 参数控制,默认值为 5;每个缓冲区的大小默认为 1GB。
注意:这 5 个缓冲区是池化复用的,并非启动时一次性全部占用。实际占用取决于
transientStorePoolSize和mappedFileSizeCommitLog的配置值。
如果 -XX:MaxDirectMemorySize 设置的值过小,无法满足 TransientStorePool 初始化时的内存申请,JVM 就会抛出 Direct buffer memory 错误。
解决方案
方案一(推荐):将直接内存设为 6GB 或更高:
-e "JAVA_OPT_EXT=-XX:MaxDirectMemorySize=6g ..."
方案二:在 broker-a.properties 中调整 TransientStorePool 的缓冲区数量:
transientStorePoolSize=4
或者直接禁用:
transientStorePoolEnable=false
注意:方案二会降低写入性能,非必要不推荐。
教训:堆外内存池是 RocketMQ 高性能的关键,但它需要足够的直接内存支撑。生产环境下建议 MaxDirectMemorySize 至少为 -Xmx 的 2-3 倍,同时根据实际负载调整 transientStorePoolSize。
四、JDK 8 不支持 --add-exports
现象
NameServer 启动报错:
Unrecognized option: --add-exports
Error: Could not create the Java Virtual Machine.
原因
--add-exports 是 JDK 9+ 引入的模块化系统参数,用于跨模块导出包。如果底层的 JDK 是 8,JVM 不认识这个参数,直接拒绝启动。
解决方案
直接移除 JAVA_OPT_EXT 中的 --add-exports 参数,或者添加 -XX:+IgnoreUnrecognizedVMOptions 让 JVM 忽略它:
-e "JAVA_OPT_EXT=-XX:+IgnoreUnrecognizedVMOptions ..."
教训:如果你的镜像基于 JDK 8(官方 apache/rocketmq:5.5.0 默认使用 eclipse-temurin:8),请避免使用 JDK 9+ 特有的 JVM 参数。
五、SELinux 阻止容器写入挂载目录
现象
目录权限(chown + chmod)全部正确,甚至设置了 777,但容器内仍无法写入文件:
touch: cannot touch '/home/rocketmq/logs/test.txt': Permission denied
原因
CentOS / RHEL 系列操作系统默认启用 SELinux。即使宿主机目录权限放开了,SELinux 策略也会阻止容器进程写入挂载的目录。
解决方案
方案一(临时测试):
sudo setenforce 0
方案二(永久关闭,生产环境慎用):
sudo sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config
sudo reboot
方案三(给目录打标签,推荐):
sudo chcon -Rt svirt_sandbox_file_t /data/docker-containers/rocketmq/broker
教训:权限问题不只有 chown 和 chmod,SELinux 是隐藏的大坑。遇到诡异权限问题时,先 setenforce 0 测试一下。
六、logs/rocketmqlogs 子目录需提前创建
现象
宿主机挂载目录 logs/ 为空,即使 Broker 启动成功也没有日志文件。
原因
RocketMQ 的日志配置 rmq.broker.logback.xml 中,日志输出路径为 ${user.home}/logs/rocketmqlogs/。容器在启动时,如果 /home/rocketmq/logs/rocketmqlogs/ 目录不存在,可能因权限或挂载机制导致无法自动创建。
解决方案
在宿主机上提前创建好 rocketmqlogs 子目录:
sudo mkdir -p /data/docker-containers/rocketmq/broker/a/logs/rocketmqlogs
sudo chown -R 3000:3000 /data/docker-containers/rocketmq/broker/a/logs
sudo chmod -R 755 /data/docker-containers/rocketmq/broker/a/logs
教训:不要依赖容器内的程序自动创建多层目录,尤其是涉及 Docker 挂载时,提前创建是更保险的做法。
七、brokerName 和 brokerId 配置要唯一
现象
集群中某个从节点无法启动,日志卡在 logback 配置加载后,无任何错误信息。
原因
主从配置中,从节点的 brokerName 与主节点重复,或 brokerId 未设置为唯一值,导致 Broker 注册到 NameServer 时发生冲突。
解决方案
确保每个 Broker 节点的配置文件中的关键字段正确:
# 主节点
brokerName=broker-a
brokerId=0
# 从节点
brokerName=broker-a-s
brokerId=1 # 从节点 > 0
教训:两主两从集群中,4 个 Broker 的 brokerName 必须两两成对(主从相同),但主从之间用 brokerId 区分(主=0,从>0)。
八、--network host 模式下 -p 端口映射失效
现象
启动容器时明明指定了 -p 10911:10911,但 ss -tlnp 看不到端口监听。
原因
当使用 --network host 时,容器直接使用宿主机网络栈,所有端口映射(-p)参数会被忽略,且 Docker 会输出警告:
WARNING: Published ports are discarded when using host network mode
解决方案
- 如果需要端口映射,不要使用
--network host。 - 在分布式部署中,每个 Broker 部署在不同宿主机上,使用
--network host可简化网络配置,此时-p可以移除。
教训:--network host 是一把双刃剑,简化网络配置的同时也会让端口管理更加透明(也可能更混乱)。
九、store 目录为空(正常现象)
现象
启动 Broker 后,挂载的 store 目录为空,没有任何子目录。
结论
这是正常的。store 目录下的 commitlog、consumequeue 等子目录只有在 Broker 收到并存储消息后才会自动创建。刚启动且未收发消息时,store 目录为空是预期行为。
发送一条测试消息后,目录结构会自动生成。
教训:不要因为空目录就以为 Broker 没启动好,docker logs 看到 boot success 才是关键证据。
十、总结:一键部署脚本
以下是集成了所有踩坑修复的最终部署脚本(单节点模板):
#!/bin/bash
# ============================================================
# RocketMQ 5.5.0 Broker 部署脚本(含所有坑修复)
# 用法:在每台机器上执行,修改 BROKER_ROLE 和 LISTEN_PORT
# ============================================================
BROKER_ROLE="a" # 根据节点修改:a, a-s, b, b-s
LISTEN_PORT="10911" # 根据节点修改
NAMESRV_ADDR="172.16.10.90:9876;172.16.10.91:9876"
# 1. 创建目录(包含 rocketmqlogs 子目录)
mkdir -p /data/docker-containers/rocketmq/broker/${BROKER_ROLE}/logs/rocketmqlogs
mkdir -p /data/docker-containers/rocketmq/broker/${BROKER_ROLE}/store
mkdir -p /data/docker-containers/rocketmq/broker/conf
# 2. 修复权限(注意:UID 是 3000!)
sudo chown -R 3000:3000 /data/docker-containers/rocketmq/broker/${BROKER_ROLE}
sudo chmod -R 755 /data/docker-containers/rocketmq/broker/${BROKER_ROLE}
# 3. 关闭 SELinux(或使用 chcon 打标签)
sudo setenforce 0
# 4. 启动容器
docker run -d \
--restart=always \
--privileged=true \
-p ${LISTEN_PORT}:${LISTEN_PORT} \
--volume /data/docker-containers/rocketmq/broker/${BROKER_ROLE}/logs:/home/rocketmq/logs \
--volume /data/docker-containers/rocketmq/broker/${BROKER_ROLE}/store:/home/rocketmq/store \
--volume /data/docker-containers/rocketmq/broker/conf/2m-2s-sync:/home/rocketmq/rocketmq/conf/2m-2s-sync \
--name broker-${BROKER_ROLE}-${LISTEN_PORT} \
--network host \
-e TZ=Asia/Shanghai \
-e NAMESRV_ADDR="${NAMESRV_ADDR}" \
-e "JAVA_OPT_EXT=-Xss512k -Xms512m -Xmx2048m -XX:MaxDirectMemorySize=6g -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=256m" \
registry.cn-hangzhou.aliyuncs.com/sinhy/rocketmq:5.5.0 \
sh mqbroker -c /home/rocketmq/rocketmq/conf/2m-2s-sync/broker-${BROKER_ROLE}.properties
一句话锦囊
RocketMQ 5.5.0 官方镜像部署四件套:UID 用 3000,JVM 用 JAVA_OPT_EXT,直接内存给 6G,SELinux 关掉。
192

被折叠的 条评论
为什么被折叠?



