RocketMQ 5.x 部署、可靠消费与高可用架构手册
先从一条“发送成功但业务没发生”的消息开始
订单服务记录了一次成功发送,库存服务却没有扣减。Dashboard 里 topic 存在,Broker 进程也在,排查仍然可能走错方向:发送端拿到的是 Broker 存储确认还是仅完成本地调用;消息进入了哪个队列;消费者属于哪个 group;消费位点是否推进;失败消息正在等待重试还是已经进入死信队列;主节点切换时成功响应对应的数据是否已经复制。
RocketMQ 把一条消息的生命周期拆成生产、存储和消费三个阶段。Producer 从 NameServer 获取路由,直接连接 Broker 或通过 Proxy 使用 5.x gRPC 客户端;Broker 把消息追加到 CommitLog,再建立 ConsumeQueue 等逻辑索引;Consumer 以 consumer group 身份订阅 topic,处理完成后提交消费结果。任何一个阶段的“可用”都不能代表整条链路可靠。
最有用的第一张排障图不是产品组件图,而是证据顺序:
排障时依次回答四个问题:路由是否正确、Broker 是否真正落盘、目标 group 是否看到待消费消息、业务处理是否幂等并成功确认。只看 Dashboard 中“topic 存在”最多证明元数据存在。
认识会影响操作结果的组件
NameServer 只保存路由,不保存业务消息
NameServer 接收 Broker 注册并向客户端提供 topic 路由。它本身是无状态组件,多个 NameServer 之间不复制数据,客户端通常配置多个地址并逐个查询。NameServer 不参与消息持久化,也不负责主从选举;删除 NameServer 数据目录不能恢复或清理消息。
NameServer 默认监听 9876。它短暂不可用时,已有客户端可能继续使用缓存路由,但新建连接、路由变更和故障切换会受影响。因此“现有生产者还能发”不能证明路由层健康。
Broker 同时承担存储、投递和消费进度
Broker 按 broker group 组织实例。传统主从模式中,brokerId=0 表示 Master,正整数表示 Slave;Controller 模式由 Controller 分配角色,不再手工指定 brokerId 和 brokerRole。一个 topic 由多个 MessageQueue 组成,队列分散在不同 Broker group 上,Producer 以队列为写入单位,Consumer group 内的实例共同分担队列。
常见端口为:
10911:Broker Remoting 主监听端口。10909:通常是 fast remoting 端口。10912:HA 复制端口。
9876:NameServer。8080/8081:Proxy 的 HTTP/gRPC 接口;5.x Java 客户端通常使用 Proxy endpoint。
端口通不等于协议正确。把 gRPC 客户端指向 10911,或者把传统 Remoting 客户端指向 Proxy,都会表现为连接失败、协议握手失败或持续超时。
Proxy 是协议入口,不是第二份消息存储
RocketMQ 5.x Proxy 为新客户端提供统一 endpoint,并负责协议转换、路由和客户端会话。Proxy 可以与 Broker 同进程,也可以独立部署;独立部署更容易水平扩容和隔离网关资源,但会增加一跳网络、独立容量和故障域。
Proxy 故障不会删除 Broker 上的消息,却会让只使用 gRPC endpoint 的客户端全部不可用。生产拓扑中至少要保证 endpoint 后面有多个 Proxy 实例,并让负载均衡健康检查同时验证端口和业务请求,而不是只验证 TCP 建连。
Dashboard 是观察入口,不是链路判据
Dashboard 能查看 topic、consumer group 和消息轨迹,但它不替代 Producer 的发送回执、mqadmin consumerProgress、Broker 日志和业务幂等记录。Dashboard 默认暴露的集群元数据本身也属于敏感信息,应只放在内网或受身份代理保护的入口后面。
选择部署形态之前先明确故障预算
单 Broker:学习最快,任何故障都会中断
一个 NameServer、一个 Broker、一个 Proxy 适合个人开发、接口联调和短生命周期测试。它能验证 topic、group、发送、消费、重试和积压,却没有副本:Broker 进程、磁盘或宿主机任一故障都会使消息服务中断,并可能造成数据丢失。
单机多实例可以用不同端口和独立 storePathRootDir 在一台机器上模拟多个 Broker group。它适合测试路由和队列分布,不提供宿主机级容灾,反而更容易发生磁盘、页缓存和网络带宽争用。
静态主从:复制数据,但切换能力取决于模式
传统一主一从或一主多从由 Master 接收写入,Slave 同步 CommitLog。SYNC_MASTER 等待同步复制后再返回,数据安全更强但延迟更高,并受 Slave 状态影响;ASYNC_MASTER 更快,但 Master 突然损坏时可能丢失尚未复制的数据。
静态主从解决的是副本问题,不自动等于高可用。没有 Controller 或 DLedger 选举时,Master 故障后的角色切换仍需要明确流程。读者应把“有 Slave”“能自动切换”“成功响应不丢消息”拆成三个独立验收项。
Controller 模式:为原生主从增加自动选主
RocketMQ 的自动主从切换文档把 Controller 作为选主组件。Controller 可以独立部署,也可以嵌入 NameServer;要容忍 Controller 节点故障,需要三个或更多副本并依赖多数派。单 Controller 故障不会立即中断现有 Master 的收发,但会失去自动切换能力。
Controller 与 Broker 的关键配置会直接改变数据安全边界。Controller 侧控制是否允许从同步集合外选主:
enableElectUncleanMaster=falseBroker 侧配置自动切换、同步确认和最小同步副本:
enableControllerMode=true
controllerAddr=controller-0:9877;controller-1:9878;controller-2:9879
allAckInSyncStateSet=false
minInSyncReplicas=2
haMaxTimeSlaveNotCatchup=15000enableElectUncleanMaster=false 避免从 SyncStateSet 之外选举落后副本;代价是副本不足时宁可不可写,也不冒险丢消息。minInSyncReplicas 决定最少同步副本数,设得越高,故障时拒写越早,数据安全越强。allAckInSyncStateSet=true 要求成功响应前同步到当前 SyncStateSet 的全部副本,延迟和可用性代价都更明显。
Controller 自己的 controllerStorePath 与 Broker 的 epoch 文件都是恢复状态,不能把它们当缓存目录删除。演练必须同时覆盖 Controller 少数派、Broker Master 退出、Slave 落后和网络分区,而不是只 kill 一个进程后看新 Master 出现。
DLedger:Raft 复制方案,迁移路径必须单独评估
RocketMQ-on-DLedger 使用 Raft 组完成日志复制与 Leader 选举,一个组通常至少三个节点。它能够自动切换,但并非 Controller 模式的同义词。官方迁移说明明确指出,DLedger 与原生主从的数据格式不同,迁移到 Controller 架构不能直接带数据原地升级;应通过停止某组写入、消费完旧消息、双向消费或业务迁移完成退场。
已有 DLedger 集群不要因为“Controller 更新”就直接改配置重启。先盘点存储格式、客户端路由、未消费消息、回滚入口和每个 Broker group 的迁移顺序。
多 Broker group:水平扩展的基本单元
生产集群通常包含多个 Broker group,每组各自提供副本,topic 的队列分散在各组。增加 group 扩展存储和吞吐,增加同组副本提高容灾能力;两者解决的问题不同。只增加副本不会提升单 topic 的并行写队列数,只增加 group 而不配置副本则扩大了单盘故障面。
托管 RocketMQ 可以减少机器、复制和升级运维,但不会替团队决定 topic 数、队列数、消费幂等、重试策略、数据分级和成本预算。选型要把控制面 SLA、数据导出能力、网络费用、版本兼容与退出成本写进决策记录。
二进制模式先看清真实进程
Linux、Unix 或 macOS 可以直接使用预编译二进制包。下载 rocketmq-all-5.5.0-bin-release.zip 并校验发布页提供的签名或校验值,解压后从发行目录启动:
unzip rocketmq-all-5.5.0-bin-release.zip
cd rocketmq-all-5.5.0-bin-release
nohup sh bin/mqnamesrv > namesrv.out 2>&1 &
tail -f ~/logs/rocketmqlogs/namesrv.logNameServer 成功后,让 Broker 与 Proxy 在同一进程中启动:
nohup sh bin/mqbroker \
-n 127.0.0.1:9876 \
--enable-proxy \
> broker-proxy.out 2>&1 &
tail -f ~/logs/rocketmqlogs/proxy.log这种 local deployment mode 便于学习和单机验证。Broker、Proxy 和存储共享故障域,不能据此推导生产 Proxy 容量或副本切换。停止时先停 Broker,再停 NameServer:
sh bin/mqshutdown broker
sh bin/mqshutdown namesrv直接杀进程可能让当前写入只依赖恢复流程完成收尾;正常停止命令也应成为升级和自动化脚本的一部分。
在本机搭一条可反复重置的容器链路
以下环境把 Remoting 链路限制在 Compose 网络内,宿主机应用通过 Proxy 127.0.0.1:8081 接入。这样可以避免容器 Broker 注册一个宿主机无法访问的地址。版本固定为官方发布的 5.5.0;升级时应先核对下载页和镜像发布状态。
目录结构:
rocketmq-dev/
├─ compose.yaml
├─ conf/
│ └─ broker.conf
└─ data/
└─ broker/conf/broker.conf:
brokerClusterName=DevCluster
brokerName=broker-a
brokerId=0
brokerRole=ASYNC_MASTER
flushDiskType=ASYNC_FLUSH
namesrvAddr=namesrv:9876
brokerIP1=broker
listenPort=10911
autoCreateTopicEnable=false
autoCreateSubscriptionGroup=false
storePathRootDir=/home/rocketmq/store
storePathCommitLog=/home/rocketmq/store/commitlog
fileReservedTime=24
deleteWhen=04这里把 brokerIP1 设为 Compose 服务名 broker,因此容器内 Proxy 和 CLI 能访问 Broker;宿主机传统 Remoting 客户端不能直接使用这条路由。若项目必须验证 Remoting 客户端,应把 brokerIP1 改成宿主机或局域网可达地址,并重新验证容器到该地址、宿主机到该地址两条路径。
compose.yaml:
services:
namesrv:
image: apache/rocketmq:5.5.0
container_name: rmq-namesrv
command: sh mqnamesrv
ports:
- "127.0.0.1:9876:9876"
networks: [rocketmq]
restart: unless-stopped
broker:
image: apache/rocketmq:5.5.0
container_name: rmq-broker
command: sh mqbroker -c /home/rocketmq/broker.conf
environment:
NAMESRV_ADDR: namesrv:9876
volumes:
- ./conf/broker.conf:/home/rocketmq/broker.conf:ro
- ./data/broker:/home/rocketmq/store
depends_on: [namesrv]
networks: [rocketmq]
restart: unless-stopped
proxy:
image: apache/rocketmq:5.5.0
container_name: rmq-proxy
command: sh mqproxy
environment:
NAMESRV_ADDR: namesrv:9876
ports:
- "127.0.0.1:8080:8080"
- "127.0.0.1:8081:8081"
depends_on: [broker]
networks: [rocketmq]
restart: unless-stopped
networks:
rocketmq:
driver: bridge在 Linux 上先保证 data/broker 对容器用户可写。Windows 和 macOS 使用 Docker Desktop 时先直接创建目录;若 Broker 日志出现 Permission denied,再按当前文件共享机制修正权限,不要为了省事把整个项目目录改成全局可写。
启动并检查三个角色:
docker compose up -d
docker compose ps
docker compose logs --tail=80 namesrv
docker compose logs --tail=120 broker
docker compose logs --tail=120 proxyNameServer 日志应出现启动成功信息,Broker 日志应显示向 namesrv:9876 注册,Proxy 日志不应持续出现获取路由或连接 Broker 失败。若只有端口监听而 Broker 未注册,先查 NAMESRV_ADDR 和 Compose DNS,不要急着重建 volume。
正向实验:从资源创建到消费位点推进
先显式创建 topic 和 consumer group,避免拼写错误在测试环境中被自动创建能力掩盖:
docker compose exec broker sh mqadmin updatetopic \
-n namesrv:9876 \
-c DevCluster \
-t demo_order_events \
-r 4 \
-w 4
docker compose exec broker sh mqadmin updateSubGroup \
-n namesrv:9876 \
-c DevCluster \
-g demo_inventory_consumer-r/-w 是 topic 的读写队列数。它们控制并行度和路由粒度,不是消息副本数;副本由 Broker group 的复制架构决定。单 Broker 开发环境设置四个队列只会得到四个逻辑队列,不会生成四份数据。
先启动官方示例消费者,再在另一终端运行生产者:
docker compose exec \
-e NAMESRV_ADDR=namesrv:9876 \
broker sh tools.sh org.apache.rocketmq.example.quickstart.Consumerdocker compose exec \
-e NAMESRV_ADDR=namesrv:9876 \
broker sh tools.sh org.apache.rocketmq.example.quickstart.Producer官方 quickstart 默认使用 TopicTest 和示例 group,因此它只能证明基础 Broker 链路,不能作为 demo_order_events 已接入项目的证据。下面用 5.x Java SDK 跑项目自己的 topic 和 group;依赖版本与服务端发行线保持一致:
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client-java</artifactId>
<version>5.5.0</version>
</dependency>先启动消费者。它只在业务处理完成后返回成功,并把 message ID 与业务键写入日志:
ClientServiceProvider provider = ClientServiceProvider.loadService();
ClientConfiguration configuration = ClientConfiguration.newBuilder()
.setEndpoints("127.0.0.1:8081")
.build();
FilterExpression all = new FilterExpression("*", FilterExpressionType.TAG);
PushConsumer consumer = provider.newPushConsumerBuilder()
.setClientConfiguration(configuration)
.setConsumerGroup("demo_inventory_consumer")
.setSubscriptionExpressions(Map.of("demo_order_events", all))
.setMessageListener(message -> {
String eventId = message.getKeys().stream().findFirst().orElse("missing-key");
System.out.printf("consumed messageId=%s eventId=%s%n",
message.getMessageId(), eventId);
return ConsumeResult.SUCCESS;
})
.build();再发送一条带稳定业务键的消息,并保留 Broker 返回的 message ID:
try (Producer producer = provider.newProducerBuilder()
.setClientConfiguration(configuration)
.setTopics("demo_order_events")
.build()) {
String eventId = "evt-order-sdk-001";
Message message = provider.newMessageBuilder()
.setTopic("demo_order_events")
.setKeys(eventId)
.setTag("ORDER_CREATED")
.setBody(("{\"eventId\":\"" + eventId + "\"}")
.getBytes(StandardCharsets.UTF_8))
.build();
SendReceipt receipt = producer.send(message);
System.out.printf("sent messageId=%s eventId=%s%n",
receipt.getMessageId(), eventId);
}消费者进程需要保持存活;关闭时显式调用 consumer.close()。这组实验至少保留三类证据:发送回执中的 message ID、消费者日志中的同一 event ID、consumerProgress 中 demo_inventory_consumer 的 diff 下降。若 SDK 无法连接而 quickstart 成功,优先检查 127.0.0.1:8081 是否确实是宿主机可达的 Proxy gRPC 入口,而不是把 10911 的 Remoting 端口误当成 5.x endpoint。
查看 topic 路由、状态与消费积压:
docker compose exec broker sh mqadmin topicRoute \
-n namesrv:9876 -t demo_order_events
docker compose exec broker sh mqadmin topicStatus \
-n namesrv:9876 -t demo_order_events
docker compose exec broker sh mqadmin consumerProgress \
-n namesrv:9876 -g demo_inventory_consumer判断闭环不是“命令退出码为 0”,而是:topic 路由指向预期 Broker;生产后最大位点增加;消费者启动后目标 group 的消费位点推进;停止生产后 diff 最终回到稳定基线。如果 diff 下降而业务状态没有变化,问题已经从 Broker 投递缩小到业务处理或幂等逻辑。
反向实验:主动制造积压和路由错误
停消费者,观察积压而不是猜测
停止项目消费者,连续发送一批带唯一业务键的消息。随后每隔一段时间执行:
docker compose exec broker sh mqadmin consumerProgress \
-n namesrv:9876 -g demo_inventory_consumer预期证据是 Broker offset 继续增加、consumer offset 不变、diff 增长。恢复消费者后,diff 应持续下降。若消费者进程在线但 diff 不下降,继续检查订阅 topic/tag 是否一致、队列是否被其他实例分配、消费线程是否阻塞以及失败消息是否进入重试。
积压恢复时间不能用固定阈值拍脑袋。先测稳定消费速率 C、正常生产速率 P 和积压量 B;只有 C > P 时,理论恢复时间才近似为 B / (C - P)。业务平均耗时、队列数和消费者实例数决定 C 的上限。
写错 NameServer 或 endpoint,区分发现失败与协议失败
把开发配置临时改成不存在的 NameServer:
127.0.0.1:19876传统客户端应出现路由查询超时或无可用 NameServer,Broker 日志不会出现这次发送。改回正确地址后重新发送并核对 message ID。若 5.x gRPC 客户端把 endpoint 写成 127.0.0.1:10911,常见证据是握手或请求超时,因为该端口不是 Proxy gRPC 入口。
这两个错误都可能被应用包装成“发送失败”,但修复位置完全不同:前者修发现地址,后者修协议入口。团队日志必须打印脱敏后的 endpoint 类型和目标地址,不能只打印统一异常文案。
消费确认、重试与死信不是同一个开关
RocketMQ 5.x PushConsumer 的消息大致经历 Ready -> Inflight -> Commit;处理失败或超时则进入 WaitingRetry,超过最大重试次数后进入 DLQ。官方消费重试说明强调:重试用于处理偶发业务失败,不应当充当限流或业务分流机制。
一个可靠消费者应遵循下面的顺序:
读取 message ID、业务键和重试次数。用业务唯一键执行幂等检查。在本地事务内写业务状态和消费记录。
本地事务成功后才返回消费成功。对可恢复错误返回失败;对永久错误记录原因并进入受控人工处理,不无限重试。
失败重试意味着同一业务消息可能被多次处理。Producer 的发送重试也可能在客户端未收到响应时产生重复,因此“Broker 至少一次投递 + 消费幂等”才是可执行的可靠性基线。不要用 message ID 作为唯一业务幂等键:重试、转发或业务重放可能生成新的消息 ID,订单号加事件类型或显式 event ID 更稳定。
重试和死信的观察入口:
docker compose exec broker sh mqadmin topicList -n namesrv:9876
docker compose exec broker sh mqadmin consumerProgress \
-n namesrv:9876 -g demo_inventory_consumer传统命名中常见 %RETRY%<group> 和 %DLQ%<group> 系统 topic。不要在自动化脚本里假设所有客户端版本和消费类型都完全采用相同内部表现,应以目标版本的 group 配置、Dashboard 和 mqadmin 输出交叉确认。
死信不是垃圾桶。团队要记录原 topic、group、message ID、业务键、最后异常和处理 owner;重放前先修复根因,再用新 group 或受控工具重投,并再次执行幂等检查。直接清空 DLQ 会同时删除故障证据和业务补偿入口。
顺序消息:只承诺消息组内顺序
顺序消息通过 message group 把同一业务键映射到同一队列并串行投递。它不是跨 topic、跨队列、跨业务键的全局顺序。
订单状态流可以使用 orderId 作为 message group,使“创建、支付、取消”在同一组内有序。代价是热点订单会把并行度压到一个组;某条顺序消息失败重试时,后续同组消息会等待,积压可能集中在少数队列。
顺序链路的判断标准包括:Producer 始终提供稳定 message group;同组消费者不并发处理;重试期间后续消息不越过失败消息;分区扩展或路由变化后仍按业务键保持顺序。若业务允许状态机拒绝旧版本事件,通常比追求全局顺序更可扩展。
事务消息:解决本地事务与发送的原子协调
事务消息采用半事务消息、执行本地事务、提交或回滚、Broker 回查的流程。它解决的是“本地事务成功但消息没发”与“消息发出但本地事务失败”的协调,不等于下游业务事务也原子完成。
事务监听器必须能够根据业务事务表幂等查询最终状态。回查不能依赖一次性的进程内变量,也不能在状态未知时随意提交。生产前应演练:Producer 在本地事务提交后、返回 Broker 前退出;Broker 发起回查;新实例根据事务记录返回一致结论;下游仍通过幂等消费处理重复投递。
对于多数业务,数据库事务内写 outbox,再由可靠发布器发送普通消息更容易观测和补偿。事务消息适合团队已具备回查状态、事务表治理和故障演练能力的场景,不应只因为“少写一张表”就采用。
CommitLog、ConsumeQueue 与清理机制
Broker 先顺序追加 CommitLog,再构建 ConsumeQueue 逻辑索引;按 key 查询还会使用 IndexFile。顺序追加适合高吞吐,但读取、消费进度和磁盘回收依赖派生索引与文件滚动。出现发送成功、消费异常时,先判断 CommitLog 是否写入,再判断 ConsumeQueue 是否构建和 group offset 是否推进。
关键存储参数的含义:
storePathRootDir=/data/rocketmq/store
storePathCommitLog=/data/rocketmq/store/commitlog
flushDiskType=SYNC_FLUSH
brokerRole=SYNC_MASTER
fileReservedTime=72
deleteWhen=04SYNC_FLUSH 把成功响应绑定到刷盘完成,降低进程或操作系统故障造成的数据风险,但增加写延迟;ASYNC_FLUSH 依赖页缓存批量刷盘,吞吐更高。fileReservedTime 与 deleteWhen 控制超过保留期的文件清理窗口,不代表消费后立即删除。磁盘不足时系统可能提前清理旧文件或拒绝写入,消息是否已消费不是唯一判断条件。
磁盘容量至少考虑:峰值写入字节率、保留时间、复制份数、索引与日志开销、重试和积压、磁盘安全水位。生产评估要用压测得到的实际消息大小和写放大,不用“每天多少条”替代字节预算。
项目接入:把协议选择和可靠性策略写进配置
5.x gRPC Java 客户端通过 Proxy endpoint 接入;传统客户端通过 NameServer 获取 Broker 路由。两条链路不要混在一个模糊的 rocketmq.address 中:
messaging:
rocketmq:
protocol: grpc
endpoints: ${ROCKETMQ_ENDPOINTS:127.0.0.1:8081}
namesrv-address: ${ROCKETMQ_NAMESRV_ADDRESS:}
topic: demo_order_events
consumer-group: demo_inventory_consumer
request-timeout: 3s
max-attempts: 3
access-key: ${ROCKETMQ_ACCESS_KEY:}
secret-key: ${ROCKETMQ_SECRET_KEY:}Producer 发送时至少设置业务 event ID、message key、event type 和 schema version。Consumer 日志只记录必要元数据与脱敏后的业务键,不打印完整消息体、token 或个人信息。
项目接入验收应覆盖四条路径:
正常发送、消费和业务状态落库。Producer 超时重试后,Consumer 幂等表只产生一条业务效果。Consumer 在业务事务提交前退出,消息重新投递后能够恢复。
Consumer 持续失败,重试次数增长并最终进入可观测的死信处理链路。
故障证据如何反推根因
No route info of this topic
先用 topicRoute 验证资源是否存在,再核对 NameServer 地址和 Broker 注册。若 topic 不存在,执行受审计的初始化脚本;不要在生产开启自动创建来掩盖拼写错误。若 topic 存在但客户端仍报错,检查客户端是否连到了另一个环境。
Broker 注册了不可达地址
容器、宿主机和远程开发机看到的网络不同。topicRoute 返回的 Broker 地址必须从客户端所在网络可达。用 Test-NetConnection、nc 或应用同运行环境中的 TCP 检查验证,不要只在宿主机测试。
发送延迟突然升高
把证据分为网络 RTT、Broker 请求排队、磁盘刷写、同步副本等待和 JVM 暂停。SYNC_FLUSH 或 allAckInSyncStateSet 下,落后副本和慢盘会直接进入发送延迟;盲目增加客户端超时只会积累更多在途请求。
消费者在线但积压不下降
依次检查 group 是否正确、订阅表达式是否一致、队列分配是否覆盖、处理线程是否阻塞、业务依赖是否超时、失败消息是否反复重试。消费实例数超过队列数时,多余实例不会提高并行度;先增加队列并评估顺序边界,再扩消费者。
切换后出现消息缺口或重复
缺口优先核对故障前成功回执、Master/Slave CommitLog 对齐、SyncStateSet 和是否允许不干净选举;重复则核对 Producer 重试、Consumer offset 提交与业务事务顺序。高可用切换通常能恢复服务,但不会替应用消除至少一次语义带来的重复。
权限、凭证和敏感数据
RocketMQ 安全基线指出,未启用 ACL 时协议层不会验证客户端身份。ACL 2.0 在 5.3.0 引入,ACL 1.0 已从 5.3.3 移除;5.5 集群应按 ACL 2.0 配置和客户端兼容性执行迁移。
在隔离环境启用 ACL 2.0 时,Broker 至少要建立认证、授权元数据提供者和内部管理身份。下面的值是本地实验占位符,真实密码由密钥系统注入,不进入仓库:
authenticationEnabled=true
authenticationMetadataProvider=org.apache.rocketmq.auth.authentication.provider.LocalAuthenticationMetadataProvider
authorizationEnabled=true
authorizationMetadataProvider=org.apache.rocketmq.auth.authorization.provider.LocalAuthorizationMetadataProvider
initAuthenticationUser={"username":"rocketmq","password":"replace-admin-password"}
innerClientAuthenticationCredentials={"accessKey":"rocketmq","secretKey":"replace-admin-password"}分离部署 Proxy 时,Proxy 也要启用对应认证与授权提供者,内部凭证必须与 Broker 的管理身份匹配。配置修改后重启隔离集群,先从日志确认认证和授权组件加载成功,再创建最小权限用户:
docker compose exec broker sh mqadmin createUser \
-n namesrv:9876 -c DevCluster \
-u producer_user -p replace-producer-password -t Normal
docker compose exec broker sh mqadmin createUser \
-n namesrv:9876 -c DevCluster \
-u consumer_user -p replace-consumer-password -t Normal
docker compose exec broker sh mqadmin createAcl \
-n namesrv:9876 -c DevCluster \
-s User:producer_user -r Topic:demo_order_events -a Pub -d Allow
docker compose exec broker sh mqadmin createAcl \
-n namesrv:9876 -c DevCluster \
-s User:consumer_user \
-r Topic:demo_order_events,Group:demo_inventory_consumer \
-a Sub -d Allow
docker compose exec broker sh mqadmin getAcl \
-n namesrv:9876 -c DevCluster -s User:consumer_usercreateUser 的密码会进入命令参数,这套命令只适合一次性本地实验。共享环境应通过受控供应任务注入凭证,并限制 CI 日志、shell history 和进程查看权限;创建后立即轮换示例值。
授权闭环要用真实 SDK 身份验证:producer_user 可以向 demo_order_events 发布,但向预先创建且未授权的 demo_other_project_events 发布必须返回授权错误;consumer_user 可以订阅目标 topic/group,换成 demo_other_project_consumer 必须被拒绝。若反例仍成功,检查客户端是否真正携带了预期 access key、Broker 与 Proxy 是否都启用授权、ACL 资源名是否匹配,以及旧长连接是否仍缓存权限。只看到 getAcl 列出规则不算授权已经生效。
Broker、Proxy 和 Controller 间通信也需要一致的内部凭证。应用账号只能拥有目标 topic 的发送或消费权限,不能复用管理员身份。AK/SK 放入密钥管理系统,通过运行时注入并定期轮换;日志、线程 dump、Dashboard 截图和错误响应都要避免泄露凭证。
安全闭环至少验证两次:应用账号可以访问自己的 topic;同一账号访问另一个项目 topic 被明确拒绝。只证明“正确凭证能连上”不能证明最小权限生效。
NameServer、Broker、Proxy 和 Dashboard 都不应直接暴露公网。必须跨网络访问时,使用私网、VPN、受控网关、TLS 和来源限制;Dashboard 还需要独立身份层和只读角色。消息体若包含个人数据、密钥、令牌或支付信息,还要明确加密、脱敏、保留期和删除责任。
容量、成本与长期治理
容量评审从业务不变量出发:峰值生产字节率、可接受端到端延迟、最长消费者中断时间、允许的数据丢失窗口、恢复时间目标和消息保留期。由这些量推导队列数、Broker group 数、副本数、磁盘吞吐与容量、Consumer 并行度,而不是从默认配置反推业务承诺。
团队应持续观察:
生产成功率、发送 P95/P99、超时与重试次数。topic 和 group 的最大积压量、最老未消费消息年龄、恢复斜率。重试与 DLQ 增长、单业务键的热点程度。
Broker 磁盘使用率、刷盘延迟、CommitLog 写入和复制落后。NameServer 路由、Proxy 请求失败、Controller quorum 和 SyncStateSet 变化。
每个 topic 和 group 都要登记 owner、数据分级、保留期、队列数、峰值预算、幂等键、重试与死信策略。自动创建资源在共享环境关闭,资源变更走脚本和评审。升级前验证服务端、Proxy、客户端 SDK、ACL 和 Dashboard 的组合兼容,先在隔离环境回放真实大小的脱敏消息,再做可回滚的分组升级。
高可用演练的通过标准不是“新 Master 出现”,而是故障期间成功响应对应的消息可追溯、Producer 在超时后正确重试、Consumer 恢复后积压下降、重复消息被幂等处理、Controller 和副本状态回到健康基线。
清理、重置与回滚
先停止 Producer 和 Consumer,确认测试 topic、group 和数据目录没有被其他人使用,再删除开发资源。共享环境不允许按前缀模糊删除,也不直接清空系统重试或死信 topic。
个人 Compose 环境的可逆停止:
docker compose stop
docker compose start确认不再需要数据后再执行:
docker compose down
rm -rf ./data/brokerWindows PowerShell 使用受控路径删除:
$target = (Resolve-Path -LiteralPath '.\data\broker').Path
if ($target -like "$((Get-Location).Path)*") {
Remove-Item -LiteralPath $target -Recurse -Force
}生产回滚不能通过删除 store 目录实现。配置或版本升级失败时,应按 Broker group 回退二进制与配置,保留 CommitLog、epoch 和 Controller 状态,并验证主从数据对齐。DLedger 与 Controller 的存储格式迁移尤其不能把“回滚”理解为改回一个开关。
上线前逐项证明
Producer 使用的是明确的 Proxy endpoint 或 NameServer 链路,协议与端口一致。topic、队列数和 consumer group 由脚本创建,自动创建已在共享环境关闭。发送回执、Broker 存储、消费位点和业务幂等记录能够用同一 event ID 串联。
消费失败能够观察到重试、DLQ 和人工恢复入口,没有无限重试。顺序范围限定在 message group,热点和阻塞代价已经压测。主从复制、刷盘策略、Controller quorum 和不干净选举策略与数据丢失预算一致。
积压容量、磁盘保留、恢复速率和消费者并行度来自实测基线。ACL、TLS、最小权限、凭证轮换和 Dashboard 访问控制已经做正反验证。Broker、Controller、Proxy、客户端与数据格式升级具备分组回滚和演练记录。
清理脚本只能处理本项目资源,不会删除共享 topic、group、重试或死信数据。
