RocketMQ 这名字,做后端的兄弟应该都不陌生。尤其是我这两年一直在折腾高并发相关的中间件,从 Kafka 到 RabbitMQ 再回到 RocketMQ,说实话,RocketMQ 是那种“第一眼觉得繁琐,用起来真香”的消息队列。它不像 Kafka 那样为了日志可以牺牲一些可靠性,也不像 RabbitMQ 那样在小规模场景下够用但大流量下需要各种调优。RocketMQ 在事务消息、延迟消息、顺序消息这些电商和支付场景里的表现,真就是为业务而生。
这篇文章我不打算讲那种“官网抄一遍”的部署教程,而是把我们团队从零开始接触 RocketMQ、选型、踩坑、最终把单机环境和集群环境跑通的全过程做一个梳理。不论你是即将在项目里引入消息中间件的新手,还是已经被分配了“把 RocketMQ 搭起来”这个任务却不知道怎么下手的同学,照着这篇文章的思路走一遍基本就能搞定。我会先从它的核心概念讲起,接着给出两种主流的部署方式——二进制包部署和 Docker Compose 部署,再把可视化控制台、集群模式以及新手最常见的故障排查串起来,保证你读完不只是“能部署”,而是真正理解每一步为什么这么干。
1. 内容整体设计与思路拆解
1.1 RocketMQ 到底是什么,为什么要选它
先帮你建立整体认知。RocketMQ 是阿里巴巴开源的一款分布式消息中间件,2016年捐给了 Apache 基金会,现在是 Apache 顶级项目。它的核心作用就一句话:在分布式系统里做数据的异步解耦和流量削峰。举一个最简单的场景:用户在电商平台下单后,系统需要做扣库存、发优惠券、发短信通知、更新积分等一系列操作。如果这些操作全部同步执行,用户可能要等好几秒,而且任何一个下游服务出问题都会导致下单失败。引入 RocketMQ 之后,下单服务只管把“订单已创建”这个消息往队列里一扔,立即返回“下单成功”,其它系统各自去消费这个消息做自己该做的事,互不干扰,这就是典型的异步解耦。
那为什么在众多 MQ 里选择 RocketMQ 而不是 Kafka 或者 RabbitMQ?我的判断标准是看业务场景。RabbitMQ 基于 Erlang 开发,胜在轻量可靠,社区活跃,路由功能灵活,但它的吞吐量在千万级日活业务下会显得吃力,而且消息堆积时的性能和运维复杂度会上升。Kafka 的吞吐量确实是三者中最高的,但它最初是为日志采集设计的,在“丢消息”这个问题上默认语义偏弱,虽然可以配置成不丢失,但成本和复杂度会很高。RocketMQ 则采取了一个比较折中的路线:单机吞吐量十万级,支持消息轨迹、事务消息、延迟消息、死信队列等高级特性,而且这些特性是开箱即用的,不需要像 Kafka 那样通过大量配置去补齐。
如果你去搜 RocketMQ 和 Kafka 的对比,会看到很多文章争论谁的吞吐量更高,但作为实际开发人员,我更关注的是消息的可靠性、代码的侵入成本、以及出问题之后能否快速定位。RocketMQ 的 Java 客户端和 Spring Boot Starter 都做得相当完善,团队如果以 Java 技术栈为主,学习成本和维护成本都会低很多。这不是说 Kafka 不好,而是要看团队的技术储备。
1.2 部署思路:单机先行,集群兜底
很多人一想到部署分布式中间件,就喜欢直接上集群,觉得不搞个三副本就对不起“生产环境”这四个字。我的建议正好相反。如果你是第一次接触 RocketMQ,或者只是想在测试环境跑通一个 demo,千万别一上来就搞集群。单机环境虽然不能体现分布式的高可用特性,但它能帮你把 RocketMQ 的各个环节拆开来看清楚:NameServer 是做什么的、Broker 是怎么注册上来的、Producer 和 Consumer 连接的是哪个端口,这些基本概念没有理清楚之前,直接上集群只会让你在排障时一头雾水。
这篇文章的展开顺序就是这样:先介绍 RocketMQ 的四大角色和一条消息从发到收的完整链路,再讲解单机环境下的部署。
单机部署有两种方式,一种是官方提供的二进制包,适合服务器上没装 Docker 或者对网络隔离有强制要求的场景;另一种是 Docker Compose,适合本地开发环境、CI 环境或者想快速体验的读者。两种我会分别给出详细步骤,并且标注我在实际操作中踩过的坑。之后我会单独讲可视化控制台 RocketMQ Dashboard 的部署——这个太重要了,没有它你几乎看不到 Topic 的实时消息量,排障基本靠猜。最后我会聊集群模式,但在那之前你要先理解单机的所有细节,否则集群的配置会让你更混乱。
1.3 部署前后要理清的核心架构概念
在动手敲命令之前,必须先把 RocketMQ 的架构图在脑子里刻出来。它不像很多单体中间件只有一个服务进程,而是由四个完全不同的角色组成,而且这四个角色缺一不可。
- NameServer:可以理解为整个集群的“注册中心”和“路由中心”。它负责管理 Broker 的元数据,Producer 发消息之前要先问它“我要发的 Topic 在哪个 Broker 上”,Consumer 拉消息同样要问它。NameServer 之间互相不通信,这一点和 ZooKeeper 那种需要选主的状态机不太一样。
- Broker:真正干活的角色,负责消息的存储、投递和过期清理。每个 Broker 启动后会向所有 NameServer 注册自己的地址和它承载的 Topic 信息。
- Producer:消息生产者,负责发消息。它会定期从 NameServer 拉取 Topic 路由信息,本地做缓存,并不是每发一条消息都去请求一次 NameServer。
- Consumer:消息消费者,负责从 Broker 拉取消息处理。它有两种模式,一种是集群消费,同一个消费组内的消费者共同分担消息;另一种是广播消费,每个消费者都收到全量消息。
理解这四个角色的协作方式后,你会发现部署 RocketMQ 的本质其实很简单:把 NameServer 启起来,把 Broker 启起来并告诉它 NameServer 的地址,再确保 Broker 和 NameServer 之间的端口能通,剩下的事情就是写代码发消息和收消息了。真正的难点往往在那些默认配置的“反直觉”行为上,比如 Broker 的自动创建 Topic 开关、内存设置、文件保留时间,接下来我会专门展开。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心细节解析与实操要点
2.1 部署前的环境检查和版本选择
我遇到过很多次这种场景:一个同事拿着 RocketMQ 的安装包直接跑部署脚本,然后发现启动失败,报了一堆内存溢出的错误。查到最后发现是 JDK 版本不对。RocketMQ 4.x 系列要求 JDK 8 及以上,5.x 系列推荐 JDK 8 或 JDK 11。这里有个容易被忽略的细节:RocketMQ 的启动脚本 runserver.sh 和 runbroker.sh 里有默认的 JVM 内存参数,比如 runbroker.sh 默认会给 Broker 分配 8G 堆内存,如果你的服务器内存只有 4G,那启动必然失败。
版本选择方面,如果你是想在项目里稳定使用,我个人建议选用 4.9.x 系列,因为资料最多、踩坑案例最全,Spring Boot 的整合文档大部分也是基于这个版本。如果你对延迟消息级别、POP 消费模式有特别需求,可以考虑 5.x,但至少要选 5.1.0 之后的版本,因为 5.0 刚出来时还是有不少问题需要后续版本修复的。这篇文章以下的操作演示基于 4.9.6,这个版本非常稳,我用了一年没出过幺蛾子。
哪些端口需要提前确认放通?我整理了一个表格,你在部署前最好按照这个清单检查一下防火墙和安全组,不然会出现“Broker 启了,但是生产端连不上”这种诡异的问题:
| 端口 | 用途 | 默认值 | 备注 |
|---|---|---|---|
| 9876 | NameServer 端口 | 9876 | 客户端和 Broker 都要连接 |
| 10911 | Broker 主端口 | 10911 | Producer 和 Consumer 连接 |
| 10909 | Broker 快速侦听端口 | 10909 | 4.x 默认开启,无特殊用途可忽略 |
| 10912 | Broker HA 主从同步端口 | 10912 | 主从部署时必须放通 |
| 8080 | RocketMQ Dashboard 端口 | 8080 | 可通过配置修改,避免冲突 |
这些都是经验之谈,光看官方文档真不一定能知道 10912 是干嘛的。我之前在生产环境排查主从同步问题,就是因为安全组只放通了 10911,把 10912 忘了,结果从节点的消息始终同步不过来,日志里全是连接超时。
2.2 二进制包部署需要准备哪些文件与目录
二进制部署虽然不如 Docker 省事,但它能让你清楚地看到每个进程的日志和文件结构,出了问题找起来更直接。先准备好这些东西:
- JDK 8u202 或更高版本的 JDK,用
java -version确认环境变量已经正确配置; - RocketMQ 4.9.6 的二进制包,去 Apache 官网下载,注意文件名是 rocketmq-all-4.9.6-bin-release.zip;
- Linux 服务器一台,内存建议不低于 4G,如果只是学习 2G 勉强也可以跑,但要记得修改默认的 JVM 参数。
下载完成后,我习惯把 RocketMQ 解压到 /usr/local/rocketmq 目录,并把目录结构梳理一下。bin 目录里存放的是各种启停脚本,conf 目录里存放的是配置文件,lib 目录是所有依赖的 jar 包。这里有个点要注意:不要用 root 用户直接跑 RocketMQ,因为它的脚本里对进程所属用户有检测,用 root 启动虽然加了参数也能跑,但生产环境里用 root 跑中间件本身就是一种安全隐患。我一般是新建一个名为 rocketmq 的系统用户,把 /usr/local/rocketmq 目录属主改为这个用户,再用 su 切换去执行启动命令。
部署过程中你还会发现,RocketMQ 的默认配置有两个地方是必须调整的。第一,runserver.sh 和 runbroker.sh 里的 JVM 参数,默认堆内存设得偏大,在小内存机器上要改小;第二,broker.conf 文件里的 brokerIP1 配置,在多网卡服务器上必须手动指定对外服务的 IP,否则客户端会拿到一个错误的内网地址导致连接失败。
2.3 Docker 部署为什么更简单,以及需要理解的关键配置项
如果是在自己的笔记本上做实验,或者团队已经全面容器化,我推荐用 Docker Compose 部署 RocketMQ。它不需要你关心 JDK 版本,不需要手动改脚本,镜像本身已经封装好了运行环境。但 Docker 部署有一个容易踩坑的地方,那就是容器内部的 IP 和宿主机 IP 不是一回事,你如果只是用默认参数把容器拉起来,宿主机上的客户端能连上 Broker,可一旦客户端跑到另一台机器上,就可能因为 Broker 注册到 NameServer 的地址是容器内网 IP 而无法连接。
解决思路其实简单:启动 Broker 容器的时候,通过环境变量告诉它“你在外面世界的地址是 192.168.x.x:10911”。具体做法就是用 -e brokerIP1=宿主机IP 这种方式显式指定,或者在映射端口时用 network_mode: host 让容器直接共享宿主机网络。如果是在 Mac 或 Windows 的 Docker Desktop 环境下用 port mapping,还需要额外注意容器内网 IP 的问题,这个稍后我会给出完整的 compose 文件示范。
所以别看 Docker 部署命令短,里面的概念反而比二进制部署更绕。二进制部署时 Broker 配置写在文件里,你改了什么心里门儿清;Docker 部署时配置通过环境变量注入,就要求你真正理解每个变量背后的含义。理解了 brokerIP1 是干什么的,你的 Docker 部署已经成功了一半。
3. 实操过程与核心环节实现
3.1 二进制方式部署单机 RocketMQ(完整命令流程)
二进制部署是最能帮助你理解 RocketMQ 原理的方式,即使你最终选择用 Docker,我也建议你在一台实验机上完整走一遍这个流程。整个过程分为六个步骤:准备 JDK、解压安装包、修改 JVM 参数、启动 NameServer、启动 Broker、验证。
第一步,确认 JDK 版本。我见过最坑的情况是系统里装了两个 JDK,一个 8 一个 17,而 RocketMQ 4.9.6 的某些脚本在 JDK 17 下会因为反射权限问题直接启动失败。命令是 java -version,如果是 JDK 17,最好先切回 8 再往下走。
第二步,解压安装包。我把下载好的安装包放在 /opt 目录下:
bash复制cd /opt
unzip rocketmq-all-4.9.6-bin-release.zip
mv rocketmq-all-4.9.6-bin-release /usr/local/rocketmq
第三步,修改 JVM 参数。这里我要给你一个参考值。先看 NameServer 的启动脚本 runserver.sh,用 vi 打开后找到 JAVA_OPT="${JAVA_OPT} -server -Xms4g -Xmx4g -Xmn2g" 这一段,把 4g 改成 512m,2g 改成 256m。再看 Broker 的启动脚本 runbroker.sh,找到类似 -Xms8g -Xmx8g -Xmn4g 的地方,改成 -Xms1g -Xmx1g -Xmn512m。这只是为了让小内存机器能跑起来,如果你的机器内存充足(16G 以上),保持默认值不用动。
第四步,启动 NameServer。注意我之前强调的,不要用 root 跑:
bash复制useradd -m -s /bin/bash rocketmq
chown -R rocketmq:rocketmq /usr/local/rocketmq
su - rocketmq -c "cd /usr/local/rocketmq && nohup sh bin/mqnamesrv > /usr/local/rocketmq/namesrv.log 2>&1 &"
启动后确认日志。如果看到 The Name Server boot success. serializeType=JSON 这句,就说明 NameServer 已经起来了。
第五步,创建 Broker 配置文件并启动 Broker。虽然 4.9.x 版本的 Broker 不指定配置文件也能启动,但我会建一个 broker.conf,把常用的配置提前固化下来:
properties复制brokerClusterName=DefaultCluster
brokerName=broker-a
brokerId=0
# 如果是多网卡机器,这里要改成对外服务的IP
brokerIP1=192.168.1.100
# 自动创建Topic开关,学习环境建议开启
autoCreateTopicEnable=true
# 自动创建消费组开关
autoCreateSubscriptionGroup=true
# Broker向NameServer心跳的间隔,毫秒
heartbeatThreadPoolCoreSize=8
# 存储路径
storePathRootDir=/usr/local/rocketmq/store
storePathCommitLog=/usr/local/rocketmq/store/commitlog
启动命令:
bash复制su - rocketmq -c "cd /usr/local/rocketmq && nohup sh bin/mqbroker -c /usr/local/rocketmq/conf/broker.conf > /usr/local/rocketmq/broker.log 2>&1 &"
如果看到日志出现 boot success,并且 broker.log 里出现 register broker to name server 这样的记录,说明 Broker 已经成功注册到 NameServer 上了。
第六步,用官方自带脚本验证。RocketMQ 的 bin 目录里有个 mqadmin 命令,用它查一下集群状态:
bash复制sh /usr/local/rocketmq/bin/mqadmin clusterList -n 127.0.0.1:9876
如果能看到 Broker 的地址和版本号,你的单机环境就算彻底跑通了。我用这段流程在不下十台服务器上操作过,基本不会出问题。
3.2 用 Docker Compose 快速部署 NameServer、Broker 与 Dashboard
再来看看 Docker Compose 的玩法。如果你只是想在 Windows 或者 Mac 笔记本上快速跑一个 RocketMQ 环境做联调,用 Docker 是最高效的方式。
我会建立一个 rocketmq-docker 目录,在里面放一个 docker-compose.yml。以下是我自己用的版本,各配置项都做了详细注释:
yaml复制version: "3.8"
services:
namesrv:
image: apache/rocketmq:4.9.6
container_name: rmqnamesrv
ports:
- 9876:9876
command: sh mqnamesrv
environment:
JAVA_OPT_EXT: "-Xms512m -Xmx512m -Xmn256m"
volumes:
- ./logs/namesrv:/home/rocketmq/logs
broker:
image: apache/rocketmq:4.9.6
container_name: rmqbroker
ports:
- 10909:10909
- 10911:10911
- 10912:10912
environment:
JAVA_OPT_EXT: "-Xms1g -Xmx1g -Xmn512m"
NAMESRV_ADDR: "namesrv:9876"
command: sh mqbroker -c /home/rocketmq/conf/broker.conf
volumes:
- ./conf/broker.conf:/home/rocketmq/conf/broker.conf
- ./logs/broker:/home/rocketmq/logs
depends_on:
- namesrv
dashboard:
image: apache/rocketmq-dashboard:1.0.0
container_name: rmqdashboard
ports:
- 8080:8080
environment:
JAVA_OPTS: "-Drocketmq.namesrv.addr=namesrv:9876"
depends_on:
- namesrv
broker.conf 文件我放在宿主机的 ./conf/broker.conf:
properties复制brokerClusterName=DefaultCluster
brokerName=broker-a
brokerId=0
deleteWhen=04
fileReservedTime=48
brokerRole=ASYNC_MASTER
flushDiskType=ASYNC_FLUSH
autoCreateTopicEnable=true
namesrvAddr=namesrv:9876
# 注意这个IP,按实际宿主机IP修改
brokerIP1=192.168.1.100
这里最值得强调的是 brokerIP1。在容器内,Broker 默认会把容器的 hostname 对应的 IP 注册到 NameServer,而容器每次启动 IP 都可能变,而且这个 IP 在宿主机外部是不可达的。所以必须显式指定 brokerIP1 为宿主机 IP。在 Linux 服务器上你可以用 ip addr 查一下网卡 IP,在 Mac 或 Windows 上如果你用 Docker Desktop,需要查一下 Docker 虚拟网卡的 IP,或者使用 docker compose 时把 network_mode 设置为 host,但那样你就要手动管理进程了。实测下来,最稳妥的方式还是在 compose 里指定宿主机 IP,然后在宿主机上用 mqadmin 或客户端连接。
启动命令就一行:
bash复制docker compose up -d
然后访问 http://localhost:8080,如果能看到 Dashboard 页面,并且页面里显示了 Broker 的地址,则部署成功。
3.3 可视化控制台:为什么没有它排障特别难受
我见过很多团队,RocketMQ 部署完了两个进程跑起来了就开始写代码,等到测试说“消息丢了”,才发现连个查看消息的界面都没有,只能通过命令行工具去翻消息。这个过程极其痛苦。所以我会在部署完的第一时间就装上 RocketMQ Dashboard,也就是之前的 rocketmq-console-ng。
控制台有两种获取方式。一种是用官方提供的 Docker 镜像,就是我上面 compose 文件里写的 apache/rocketmq-dashboard:1.0.0,直接拉起来就能用。另一种是下载源码自己打包,也就是有些人搜“rocketmq dashboard 打包”会遇到各种问题的那条路线。源码方式需要你先装 Maven,然后去 GitHub 拉 rocketmq-dashboard 仓库,在根目录执行 mvn clean package -DskipTests,最后把 target 下的 jar 拿出来用 java -jar 启动。我建议直接使用 Docker 镜像,省心且版本兼容性有保障。
控制台最实用的几个功能帮你罗列一下:
- 查看集群整体状态。如果你搭了主从,在这里能直观看到各个 Broker 的地址、角色、存活状态,主从是否同步一眼就知道;
- Topic 管理。可以新建 Topic、查看 Topic 的消息总量、查看消息堆积情况;
- 消息查询。可以根据 Message ID 或 Key 精确查询某条消息的内容,这在排查“消息为什么没消费”时是杀手级功能;
- Consumer 管理。看每个消费组的消费进度、消费者连接数、堆积量。
所以说你没有控制台,排查消息问题就像蒙着眼睛找东西;有了它,大部分问题都能可视化地定位出来。
3.4 Topic 和消费组:部署完第一件事该做什么
有些朋友部署完环境之后,第一件事是去看控制台的图表数据,其实第一件该做的事是创建好你的 Topic 和消费组。虽然在 broker.conf 里我设置了 autoCreateTopicEnable=true,但官方文档明确说了这个开关不推荐在生产环境开启,原因有两个。
第一,自动创建 Topic 时默认只创建在单台 Broker 上,不会自动做多副本和负载均衡,如果那台 Broker 挂了,这个 Topic 的消息就全丢了。第二,自动创建的 Topic 队列数默认是 4 个,对于需要提升并发消费能力的业务来说,队列数往往需要逐个调优。所以生产环境里更稳妥的做法是先手动建好 Topic,再关掉自动创建开关。
手动创建 Topic 的命令如下:
bash复制sh /usr/local/rocketmq/bin/mqadmin updateTopic -n 127.0.0.1:9876 -c DefaultCluster -t OrderTopic -r 8 -w 8
参数说明:-n 是 NameServer 地址,-c 是集群名,-t 是 Topic 名,-r 是读队列数,-w 是写队列数。读写队列数我一般设置为相同,默认 8。如果你在控制台里创建,只需要在 Topic 管理页选择集群,填上 Topic 名和队列数,点确定即可,底层调用的就是同样的接口。
创建消费组用这个命令:
bash复制sh /usr/local/rocketmq/bin/mqadmin updateSubGroup -n 127.0.0.1:9876 -c DefaultCluster -g OrderConsumerGroup
这一步往往被忽略,但不提前创建消费组的后果是什么呢?如果 Consumer 客户端是第一次启动,它会在 Broker 上自动注册消费组,这不算大问题;可是如果你需要给一个消费组配置“从最早开始消费”还是“从最新开始消费”的策略,或者要重置消费位点,提前创建好消费组会方便很多。
4. 常见问题与排查技巧实录
4.1 启动失败常见原因与对应解法速查
部署 RocketMQ 的过程中,几乎每个人都会遇到下面这种“日志没报错但服务就是起不来”的情况。我根据自己经历过和帮同事排查过的案例,整理成一张速查表,建议你直接收藏:
| 症状 | 可能原因 | 排查与解决 |
|---|---|---|
| NameServer 启动后马上退出 | JVM 内存参数设太大,分配失败 | 修改 runserver.sh 中的 Xms/Xmx,降低到合理值 |
Broker 启动报 java.lang.IllegalStateException |
内存不足或端口被占用 | 先 free -h 查看内存,再用 netstat -tlnp 查端口 |
| Broker 能启动但注册不上 NameServer | 9876 端口不通,或 broker.conf 配置错误 | 检查防火墙,telnet 测试 9876 连通性,确认 namesrvAddr 配置正确 |
| 客户端连接不上 Broker | brokerIP1 未配置或配成内网 IP | 查看 Broker 日志确认注册地址,改 brokerIP1 后重启 |
创建 Topic 时报 NAME_SERVER_UPDATE_TOPIC_FAILED |
broker 未成功同步或 Topic 已存在 | 先用 mqadmin topicList 确认已存在,再检查 -c 参数是否对应实际集群名 |
| Dashboard 能打开但看不到 Broker | Dashboard 配置的 NameServer 地址和 Broker 注册的不是同一个 | 检查 Dashboard 里配置的 namesrvAddr,确保网络可通 |
日志显示刷屏的 service not available |
RocketMQ 与 JDK 版本不兼容,常见于高版本 JDK | 换回 JDK 8 |
生产环境接入后消息发不出去,客户端报 connect to broker failed |
Broker 的 10911 端口被防火墙挡了 | 放通 10911 和 10912 端口 |
排查的时候我有个习惯,先看日志再查配置。RocketMQ 的日志目录默认在启动脚本执行目录下的 logs 里,NameServer 的日志文件是 namesrv.log,Broker 的日志文件是 broker.log。如果 Broker 启动时没有显式指定配置,它还会打印出当前使用的配置项,你可以在日志里快速确认 brokerIP1 到底注册成了什么。
4.2 关于集群模式:主从、多副本与事务消息
前面讲了单机部署,但如果你的项目要上线生产环境,单机是撑不住的。RocketMQ 的集群模式最小单元是“一主一从”,也就是一个 Master Broker 搭配一个 Slave Broker,Master 负责读写,Slave 负责同步复制。当 Master 宕机时,Slave 不会自动升级为 Master 对外提供服务,这是很多人对 RocketMQ 集群模式的最大误解——它做的是数据冗余,而不是故障自动转移。所以生产环境更可靠的方案是每个集群中至少保证主从都在跑,如果主挂了,Slave 上的数据还在,你可以手动把请求切过去或者等主恢复。
集群模式的部署本质上就是单机部署的叠加。你需要准备两台机器,一台部署主 Broker,一台部署从 Broker,两份 broker.conf 的主要区别在于 brokerId 和 brokerRole。主节点的 brokerId 设置为 0,brokerRole 设置为 SYNC_MASTER 或 ASYNC_MASTER;从节点 brokerId 设置为 1(非 0 即可),brokerRole 设置为 SLAVE。两个节点的 brokerName 必须相同,它们才会被认为是一个主从复制组。
properties复制# 主节点配置示例
brokerClusterName=DefaultCluster
brokerName=broker-a
brokerId=0
brokerRole=SYNC_MASTER
flushDiskType=ASYNC_FLUSH
namesrvAddr=192.168.1.10:9876;192.168.1.11:9876
storePathRootDir=/usr/local/rocketmq/store
storePathCommitLog=/usr/local/rocketmq/store/commitlog
properties复制# 从节点配置示例
brokerClusterName=DefaultCluster
brokerName=broker-a
brokerId=1
brokerRole=SLAVE
flushDiskType=ASYNC_FLUSH
namesrvAddr=192.168.1.10:9876;192.168.1.11:9876
storePathRootDir=/usr/local/rocketmq/store
storePathCommitLog=/usr/local/rocketmq/store/commitlog
这里有个生产环境经验值得分享一下:如果用了 SYNC_MASTER,写入延迟会稍微提高,但换来的是消息在主从两边都落盘后才返回成功,消息基本不会丢。如果业务上可以容忍极少量消息丢失换取更低延迟,再用 ASYNC_MASTER。电商支付的订单消息我一般会要求用 SYNC_MASTER,日志类的随便用 ASYNC 就行。
NameServer 的高可用方案则是部署至少两台,互相不通信,Broker 启动时把所有 NameServer 的地址配置进去,它会向所有 NameServer 注册。客户端启动时也会从这些地址列表里逐个获取路由信息,任何一个 NameServer 不可用,客户端依然可以继续工作。
4.3 关于批量消费、事务消息等面试和实战高频点
部署讲完了,很多人在写代码时又会遇到新问题,比如热词里出现的“Spring Boot 下 RocketMQ 批量消费”“事务消息怎么玩”。我简单把这两块的要点展开说说,因为这是从部署走向实战后最常见的两个方向。
批量消费的痛点在于 RocketMQ 默认一次最多给消费者拉取 32 条消息,而且单条消息不能太大(默认最大 4MB)。业务上真正麻烦的是“批量”的定义:RocketMQ 批量发送时有消息总量 1MB 的限制,批量消费时又需要你在监听器里自己组装 List 进行处理。如果你用 DefaultMQPushConsumer,设置 consumer.setConsumeMessageBatchMaxSize(10) 后,监听器收到的一个消息对象其实是一个封装了 10 条消息的集合,需要遍历去处理。Spring Boot 体系下,用 @RocketMQMessageListener 时,方法参数如果定义为 List<MessageExt>,每一次调用可能只包含一条消息,所以需要结合 consumeMessageBatchMaxSize 这个参数来理解。不少人在这一步栽过跟头,以为批量消费会自动把消息拼成大数组传给你,实际完全不是这样。
事务消息是 RocketMQ 的护城河功能。它的核心思想是“先发一个半消息,执行本地事务成功后提交确认,消息才会对消费者可见;如果本地事务执行失败,则回滚这条消息”。这里要理解的是,半消息发出去后,Consumer 看不到,但 Broker 已经保存了它。如果本地事务执行时间太长,Broker 会主动回查事务状态,你需要实现一个 checkLocalTransaction 的回查接口告诉 Broker 最终到底该提交还是回滚。在这套机制下,消息发送和本地数据库操作能做到最终一致性,所以你不用再费劲地自己写本地消息表去保证两边数据一致。
4.4 部署排障实录:一次类 EOFException 问题的定位过程
最后记录一次最让我印象深刻的排障过程,恰好也和很多人搜过的“rocketmq dashboard 打包报 Caused by: java.io.EOFException: SSL peer shut down”相关。
当时我在一台内网服务器上编译 rocketmq-dashboard 源码,Maven 在下载依赖时频繁报错,最后稳定地出现 java.io.EOFException: SSL peer shut down。当时第一反应是 Maven 仓库地址有问题,换了阿里云镜像还是报错。后来认真分析了异常信息,发现它是在和远程仓库建立 HTTPS 连接的过程中被中断,而内网机器经常会限制出方向的 443 端口访问,或者防火墙会对长时间未断开的 HTTPS 连接做静默重置。
解决办法其实不复杂:在内网编译机上配置 Maven 使用 HTTP 协议的镜像源,或者在防火墙放行到公网仓库的 HTTPS 访问。如果在你的场景下必须要走 HTTPS,可以尝试把 Maven 的下载线程数调低,减少同时建立的连接数:
bash复制mvn clean package -DskipTests -Dmaven.artifact.threads=1
说实话,这个报错和 RocketMQ 本身没有关系,但它确实会成为部署环节里的一块绊脚石。排查任何部署问题,逻辑上都要分层——先看网络通不通,再看仓库可达不可达,再看环境变量对不对,最后才怀疑源码和配置的问题。一上来就重装系统或者重编源码,往往浪费时间且依旧找不到根源。
5. 部署后验证与日常维护建议
环境搭建好之后,不是能启动就算完,还要做一套简单的验证流程。我每次在客户现场部署完 RocketMQ,都会按照下面的顺序过一遍,大概五分钟就能验证整套环境是否健康。
第一项是进程存活检查。执行 ps -ef | grep rocketmq 或者 jps -l,应该能看到 NamesrvStartup 和 BrokerStartup 两个进程,说明 Java 进程本身没有异常退出。
第二项是端口监听检查。执行 netstat -tlnp | grep java,确认 9876、10909、10911、10912 都在监听。这里我踩过一个坑:有时候 NameServer 进程还在,但 9876 端口变成 TIME_WAIT 状态,实际上服务已经不可用了。
第三项是用官方工具做一次基础逻辑验证。bin 目录下有个 tools.sh 脚本,可以用来发消息和收消息。先启动一个消费者:
bash复制sh /usr/local/rocketmq/bin/tools.sh org.apache.rocketmq.example.quickstart.Consumer
再打开另一个终端,启动生产者:
bash复制sh /usr/local/rocketmq/bin/tools.sh org.apache.rocketmq.example.quickstart.Producer
如果消费者窗口打印出消息内容,说明从 NameServer 寻址到 Broker 存储再到 Consumer 消费的完整链路都是通的。这一步通过,你就可以放心地开始写自己的业务代码了。
日常维护方面,最容易被忽视的其实就是磁盘和消息堆积。RocketMQ 的消息默认保留 48 小时,到期后自动删除。如果你的业务消息量大,这 48 小时里可能会占掉几百 G 的空间,所以监控磁盘使用率是必做的功课。其次就是消费堆积,控制台里查看消费组的堆积数如果持续增长,说明消费者出问题了或者消费能力不够,要尽快处理,因为堆积大量消息后即使解决了消费者问题,积压的消息也会带来很高的消费压力。
我的经验是给 RocketMQ 单独建一套监控脚本,每隔五分钟检查一下 Broker 进程、端口、磁盘和消费堆积,任何一项异常就告警。整套脚本逻辑不复杂,但能让你从“等用户反馈消息丢了”变成“主动发现问题”,整体体验完全不同。
谈到最后,关于部署这件事,我个人的体会是:RocketMQ 的部署难点从来不在敲命令本身,而在于弄懂它为什么这样设计。你理解了 NameServer 只是路由中心而不是存储中心,就不会奇怪为什么它宕机不影响已启动的 Broker 收发消息;你理解了 Broker 注册地址是 IP 而不是主机名,就不会在容器环境里踩进 brokerIP1 的坑。先把单机压熟,再去做主从,最后再根据业务情况调优 JVM 和存储配置,这套递进路线是我觉得最稳妥的上手方式。
再分享一个小技巧:平时排查 RocketMQ 问题时,多观察一下它的日志文件,尤其是 broker.log 里那种 periodic 的刷屏信息。很多看似诡异的问题,日志里其实已经写得很明白了,只是被一堆无关的 INFO 淹没容易忽略。把日志级别适当调高,或者在控制台里建好告警规则,你会发现分布式消息队列远没有传说中那么可怕,它不过是一个支撑业务的得力工具而已。
