RocketMQ性能调优全攻略:从瓶颈定位到参数配置与实战案例
做消息中间件运维和性能调优这些年我经手过的集群少说也有几十套从单机开发环境到多机房生产集群都碰过。提到 RocketMQ 性能调优很多新手的反应是“加内存、加磁盘、调线程数”但实际跑起来发现该卡还是卡。原因很简单调优不是拍脑袋改参数而是先搞清楚瓶颈在哪、数据是怎么流动的、每个环节的默认值为什么不适合你的场景。这篇文章我就把 RocketMQ 调优的完整思路拆开讲清楚从存储原理到 JVM 配置从 Producer 客户端到 Consumer 消费模型再到一次真实的线上问题排查过程需要的可以直接照着抄。我尽量不堆理论全程讲怎么做、为什么这么做、踩过哪些坑。适合已经能跑通 RocketMQ、正在为吞吐或延迟头疼的同学也适合准备做容量评估和压测的团队参考。1. 先把调优的目标和瓶颈定清楚1.1 性能调优到底在调什么三个核心指标任何中间件的性能调优最终都要落到三个指标上吞吐量、响应延迟、资源利用率。RocketMQ 也不例外。吞吐量单位时间内成功发送/消费的消息条数常用 TPS 或 TPM 表示。生产环境一般看的是“集群总吞吐”也就是所有 Broker 累加后的值。响应延迟消息从 Producer 发送到 Broker 返回写入结果的时间以及从 Consumer 拉取到消息的端到端延迟。实时业务比如风控、交易更关心前者异步场景更关心后者。资源利用率CPU、内存、磁盘 IO、网络的占用情况。资源利用率不是越低越好而是要看有没有某个资源被打满而其他资源还很空闲这叫“瓶颈失衡”。我曾经遇到一个团队说 Broker 的 CPU 使用率只有 20%但 TPS 就是上不去。一查发现磁盘 IO 已经是 90% 以上的繁忙度所有请求都在等刷盘。这就说明调优方向完全错了CPU 再便宜也没用瓶颈在磁盘。调优的第一步不是改参数而是先确定你要优化的是哪一项。高吞吐场景和低延迟场景的参数配置往往互相冲突比如批量发送能提升吞吐但会抬高单条消息的发送延迟如果一个系统同时在跑实时风控和异步追踪日志通常需要拆 Topic 并按场景分别配置客户端。1.2 调优前必须完成的三件事容量预估、压测基线、监控埋点我见过太多人一上来就改 Broker 配置结果连系统原来的水位是多少都不知道。调优前必须做三件事第一容量预估。先算一算业务高峰期的消息量。比如日均订单 5000 万平均每条消息 1KB早晚高峰 2 小时占全天 60% 的量那峰值 TPS 大概就是 5000 万乘以 60% 除以 7200 秒约 4166 条每秒。再放大 3 到 5 倍作为缓冲你的目标 TPS 至少要顶到 1.5 万以上这样才能算合格。第二压测基线。用同样的消息体大小、并发数和 Topic 数量在调整前后各做一轮压测把原始的 TPS、P99 延迟、CPU 和磁盘占用记录下来。没有基线数据后面所有的调优结论都站不住脚因为你根本不知道改动到底带来了多少收益。第三监控埋点。RocketMQ 自带的 Dashboard 只能看到大体情况真正排查问题时要靠 Prometheus 与 Grafana 这类监控体系把 Broker 的 JVM 堆内存、GC 次数、磁盘 IO 队列深度、Page Cache 命中率、网络线程池活跃度、Consumer 消费延迟等指标全部拉出来。很多问题只有把多个指标放在同一时间轴上看才能定位。我当时给团队定的规矩是没有监控不调优没有基线不改参。你只要把这个规矩守住后面遇到再诡异的问题都能复盘。1.3 常见瓶颈分布先判断是 IO、内存、CPU 还是网络从大量实际案例来看RocketMQ 的性能瓶颈可以按资源分成四类瓶颈类型典型表现直接诱因磁盘 IO刷盘耗时高、IO 队列长、TPS 上不去消息量太大、刷盘策略不合理、磁盘本身性能差内存GC 频繁、Full GC 后卡顿、堆外内存吃紧堆太小、缓存配置不当、消息大小不均CPU线程切换严重、压缩解压耗时高、序列化开销大线程数配置过大、消息体过大、业务逻辑串在客户端网络Send 线程阻塞、延迟升高、连接数不足带宽不够、TCP 参数不合适、跨机房复制一个成熟的调优流程是先判定当前系统属于哪一类瓶颈再去对应调整参数。比如 CPU 瓶颈大概率是线程配置和消息体编码问题而不是 Broker 落盘问题磁盘 IO 瓶颈则要先检查操作系统块设备和文件系统再看 RocketMQ 的刷盘策略。我给一个简单判断方法压测时用iostat看磁盘 util 和 await如果 util 超过 70% 同时 TPS 不再增长那明显是磁盘瓶颈如果磁盘很闲而 CPU 的 sys 占比很高那多半是网络栈或线程上下文切换导致的 CPU 瓶颈。方向对了调优就是套参数的事。2. Broker 端调优存储、线程与网络的核心参数2.1 刷盘策略怎么选SYNC_FLUSH 与 ASYNC_FLUSH 的取舍Broker 端第一个决策点是刷盘方式。在broker.conf中通过flushDiskType配置可选SYNC_FLUSH同步刷盘和ASYNC_FLUSH异步刷盘。同步刷盘的意思是Broker 收到消息后先把消息写入内存 Page Cache然后必须等数据真正落盘并返回成功才算这条消息写入完成。异步刷盘则是写入 Page Cache 后立即返回成功后台线程再定期把脏页刷入磁盘。很多人以为“同步一定比异步安全”但在高吞吐场景下同步刷盘的性能损耗非常大。实测在普通 SSD 上单个 Broker 同步刷盘的 TPS 通常只有 1 到 2 万而异步刷盘可以轻松跑到 5 到 8 万甚至更高。差异的核心在于磁盘的 IOPS 和延迟每一次刷盘都是一次 fsync 调用。那到底选哪种我的建议是分场景金融交易、支付对账这类丢失一条消息都无法容忍的场景必须用SYNC_FLUSH并且要配合主从同步复制brokerRoleSYNC_MASTERslaveReadEnablefalse来保证可靠。日志、监控、异步通知、活动追踪这类场景用ASYNC_FLUSH就足够了配合flushIntervalMs和flushCommitLogLeastPages控制刷盘节奏性能和可靠性可以兼顾。注意很多人忽略了一个细节即使ASYNC_FLUSH也不是完全不落盘的后台刷盘线程依然会周期性把数据写到磁盘异常断电时最多只会丢失一小段时间内未刷盘的消息。所以异步刷盘的核心价值是用“可能丢最近几十毫秒的数据”换取“吞吐提高数倍”业务能不能接受就要提前评估。2.2 文件映射与 Page Cache理解 RocketMQ 存储性能的基石RocketMQ 的高性能写入本质上靠的是mmap page cache机制。写消息时先写入 OS 的文件映射区域Page Cache再由内核后台刷盘这样写入路径绕开了传统每次写盘的系统调用性能才能上去。这也带来一个重要调优思路Broker 堆内存不要设置得过高要给 Page Cache 留出空间。你可能会觉得堆内存越大性能越好但实际上 RocketMQ 读写都大量依赖 Page CacheBroker JVM 堆内存配得太大反而压缩了 Page Cache 的可用空间。以 32GB 物理内存的机器为例我常用的分配方式是JVM 堆 8GB~10GB剩余约 20GB 留给操作系统的 Page Cache。如果堆设成 24GB留给 Page Cache 的空间就只剩几个 GB高峰期网络请求一起压迫内存时很容易触发回收反而导致写入卡顿。另一个与 Page Cache 相关的参数是transientStorePoolEnable。开启后Broker 会额外开辟一个堆外内存池DirectByteBuffer来承接写入数据再由专职线程把数据刷入 FileChannel这样能把写入路径进一步和 Page Cache 隔离减少写放大。实测在高吞吐写入场景下开启transientStorePoolEnabletrue能提升约 15%~30% 的吞吐但代价是会额外占用堆外内存建议在内存富余的 Broker 上开启。2.3 网络线程模型Listener 线程池、业务线程池与 IO 线程池Broker 的网络模型分三层Netty Boss Group负责接收连接通常线程数很小1~2 个。Netty Worker Group负责读写 Socket默认线程数为 CPU 核数。业务处理线程池由sendMessageThreadPoolNums、pullMessageThreadPoolNums、adminThreadPoolNums等参数控制。这里的常见误区是盲目调大sendMessageThreadPoolNums。线程数不是越多越好如果机器是 16 核你把它调到 64很多线程会在锁竞争和上下文切换上耗尽 CPU吞吐反而下降。一个从实践中得出的参考值是CPU 核数 * (1~2)先压测看线程池活跃度再逐步调大。如果是网络延迟敏感的跨机房场景还要关注 TCP 层面的参数。Broker 侧listenBacklog控制 accept 队列长度连接数多时要调大避免握手请求堆积导致客户端连不上。同时 Broker 的useEpollNativeSelectortrue要确认开启在高并发连接下 epoll 模型比传统 select 模型能支撑的并发连接数高出好几个量级。2.4 消费存储关键参数ConsumeQueue 构建与过期文件回收RocketMQ 的存储结构是CommitLog消息主体 ConsumeQueue逻辑消费队列 IndexFile索引。写消息只追加 CommitLogConsumer 通过 ConsumeQueue 定位消息。当消息量超大时ConsumeQueue 的构建可能成为热点。每个 Topic 的 Queue 过多、每个 Queue 的消费位点推进太慢都会让 ConsumeQueue 文件增长变快、磁盘占用升高。建议单个 Topic 的读写队列数控制在8~16之间不要动不动就是 32 或 64 个 Queue。Queue 太多会加剧 Rebalance 的复杂度也会让 Consumer 实例之间的负载分配不稳定。过期文件回收参数也要关注fileReservedTime默认 72 小时如果磁盘比较紧张且业务能接受消息快速过期可以调到 12 或 24 小时。deleteWhen04表示每天凌晨 4 点执行过期文件删除diskMaxUsedSpaceRatio75表示磁盘使用超过 75% 时就开始强制清理这两个参数建议保持默认或者结合实际磁盘水位微调。过度清理会导致大量消费延迟的消息被丢弃必须谨慎评估。3. 客户端调优Producer 与 Consumer 的正确用法3.1 Producer 端批量发送与压缩吞吐翻倍的关键手法Producer 端的调优空间往往比 Broker 端更大而且不需要重启 Broker收益来得非常直接。第一打开批量发送。DefaultMQProducer默认会等batchTimeout或者攒够maxBatchSize才真正发送把这些参数配置合理能显著提升吞吐。批量发送减少的是网络往返次数和服务端的处理次数如果你每条消息都是单独一个请求发过去那不管 Broker 多强吞吐都会被网络 RTT 卡住。我常给团队用的配置是producer.setSendMsgTimeout(3000); producer.setRetryTimesWhenSendFailed(2); producer.setBatchTimeout(5); // 最多等待 5ms producer.setMaxBatchSize(1024 * 4); // 攒够 4KB 就发 producer.setCompressMsgBodyOverHowmuch(4 * 1024); // 超过 4KB 压缩第二开启消息压缩。CompressMsgBodyOverHowmuch默认是 4KB意思是消息体超过 4KB 才触发压缩。如果你有大量 JSON 文本消息压缩能减少传输字节数和落盘占用代价是消耗一些 CPU。实测一个 100KB 的 JSON 消息GZIP 压缩后可以降到 10KB 左右吞吐提升相当可观。第三合理选择发送方式。异步发送send(Message, SendCallback)比同步发送快很多因为不用等每条消息的响应。大量场景下异步发送结合批量已经能满足绝大多数吞吐需求。像 Kafka 那样的事务消息、顺序消息要慎用异步因为顺序性需要更严格的控制。常见误区很多团队为了吞吐把 Producer 的线程池设得很大结果一条消息的发送链路里全是等待 IO 的线程线程切换开销比实际发送开销还高。我一般让生产者的发送线程数和 CPU 核数差不多然后通过批量发送提高单线程的工作效率效果反而更好。3.2 Consumer 端并发数与批量拉取延迟与吞吐的平衡Consumer 端的核心参数是consumeThreadMin、consumeThreadMax和consumeMessageBatchMaxSize。前者控制消费线程池大小后者控制单次最多拉取多少条消息。先说线程数。DefaultMQPushConsumer默认最小和最大线程数都是 20但在高并发场景下 20 个线程未必够用。如果消费端逻辑是纯解析入库、没有外部 RPC 等待20~40 个线程足够如果消费逻辑要调外部接口每个请求耗时 50ms那可能就需要 80~100 个线程来维持吞吐。线程数最终要看 Consumer 的响应时间分布而不是凭直觉。再看批量拉取。consumeMessageBatchMaxSize默认是 1也就是一次只消费一条消息。把这个值调大到 32 或 64配合消费线程池吞吐可以有几倍的差异。注意consumeMessageBatchMaxSize只是拉取上限实际拉多少取决于 Broker 端maxTransferBytesOnMessageInMemory等参数如果想拉更大的批次Broker 端也要配套调整。Consumer 端还有一类隐蔽问题消息堆积时Rebalance 频繁发生。Rebalance 期间 Consumer 会停止消费一段时间像“踢皮球”一样在多个实例间重新分配队列。此时如果consumeThreadMax太大、实例数太多每个实例都要抢占系统资源做 Rebalance反而拖垮整体消费速度。一般建议一个消费组下的实例数不要超过 Queue 总数否则多出来的实例只会空转。3.3 消费位点与堆积治理反向定位性能短板如果 Consumer 的消费延迟持续上涨先别急着加机器要反向排查消费端性能短板。最直接的方法是看消费逻辑的平均耗时。在消费回调方法里打日志或者埋点记录单条消息的处理耗时。如果平均耗时已经超过 10ms那你需要的不是更多 Consumer而是优化消费逻辑本身——比如批量入库替代单条入库、减少每次消费中的网络调用次数、用异步化把非核心逻辑剥离开。另外一个容易忽略的地方是Consumer的pullBatchSize。即使你代码里设置了consumeMessageBatchMaxSize32如果拉取线程每次只从 Broker 端拿回来 1~2 条那消费吞吐还是会受限。这需要看 Broker 端DefaultMessageStore的拉取相关参数是否被调小过。我见过某团队把maxTransferCountOnMessageInMemory调成了 8导致所有 Consumer 拉消息的批量大小全部被限制在 8 条反馈“消费变慢”把该参数恢复成默认的 32 后立刻恢复。4. JVM 与操作系统层面的隐性优化4.1 JVM 参数参考模板堆内存、GC 选择与常见坑RocketMQ Broker 本质是 Java 应用JVM 参数是性能的底层基本面。以下是我在一个 32GB 内存、16 核机器的生产环境常用的 JVM 参数模板-Xms8g -Xmx8g -XX:MetaspaceSize512m -XX:MaxMetaspaceSize512m -Xss512k -XX:UseG1GC -XX:MaxGCPauseMillis100 -XX:ParallelGCThreads8 -XX:ConcGCThreads4 -XX:DisableExplicitGC -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/data/rocketmq/logs/heapdump.hprof堆内存设置成 8GB 我是刻意为之的因为要留足够空间给 Page Cache。如果机器是 16GB 内存堆建议 4~6GB如果是 64GB 内存堆建议 12~16GB。原则是堆占物理内存的 1/4 到 1/3最多不超过 1/2。GC 选择上JDK 8 推荐 G1JDK 11 直接上 G1 或者 ZGC。RocketMQ 是延迟敏感型中间件Full GC 是杀手级问题。出现 Full GC 的第一个嫌疑就是堆太小、对象分配量超过堆容量第二个嫌疑是-XX:DisableExplicitGC未开启导致框架里的System.gc()调用触发 Full GC。这两个问题我都实际遇到过。线程栈大小-Xss512k是常见配置如果业务处理线程数比较多调成 256k 能节省不少内存。但注意如果代码递归深度大256k 可能不够稳妥起见生产环境保留 512k。4.2 操作系统层文件句柄、TCP 缓冲与内核参数RocketMQ 是网络 IO磁盘 IO 密集型的应用操作系统的几个默认参数总是需要动的。缺省情况下Linux 对单进程文件句柄数的限制是 1024对 Broker 来说远远不够每个连接都会占用一个文件句柄生产环境 Broker 并发连接数以千计很正常。# /etc/security/limits.conf * soft nofile 655350 * hard nofile 655350 * soft nproc 655350 * hard nproc 655350TCP 相关的几个内核参数也需要调整。net.core.somaxconn默认 128当连接突发增大时可能导致 accept 队列溢出net.ipv4.tcp_max_syn_backlog默认 128同样会影响短连接场景下的握手成功率。sysctl -w net.core.somaxconn4096 sysctl -w net.ipv4.tcp_max_syn_backlog4096 sysctl -w net.ipv4.tcp_fin_timeout15 sysctl -w net.ipv4.tcp_tw_reuse1如果 Broker 使用了 netty 内部连接池且大量复用长连接tcp_tw_reuse的作用相对有限但生产环境多多少少会有 TIME_WAIT 积压的问题建议一并设置。跨机房的低延迟场景重点关注tcp_rmem和tcp_wmem适当调大接收和发送缓冲区能减少小包传输的吞吐浪费。4.3 磁盘选型与 IO 调度策略RocketMQ 的存储性能上限基本由磁盘决定。SSD 的随机写和顺序写性能差异远小于机械盘所以生产环境不建议用 HDD 跑高吞吐集群。即便用的是 SSD也要注意 IO 调度策略。Linux 默认的cfq调度器在多队列 SSD 上并不合适建议改为none也就是 no-op让块设备层直接透传 IO 请求减少排队延迟。# 查看当前调度策略 cat /sys/block/vda/queue/scheduler # 临时设置为 none echo none /sys/block/vda/queue/scheduler另外RocketMQ 的 CommitLog 是典型的顺序写模型靠 Page Cache 吸收随机写请求。如果你的数据盘做了 RAID尽量使用 RAID 10 而不是 RAID 5因为 RAID5 的写惩罚write penalty在高并发写场景下非常伤校验计算和多次 IO 会让延迟翻倍。我还建议为 CommitLog 和 ConsumeQueue 使用单独的磁盘目录或其他存储设备。消费追不上时ConsumeQueue 也会大量读写如果和 CommitLog 共用一块盘两者会互相抢 IO。把storePathCommitLog与storePathConsumeQueue分开挂载是性价比很高的一种优化。5. 实战复盘一次线上吞吐骤降的完整排查过程5.1 现场现象与初步判断有一次某个模拟项目 X 的线上监控发出警报核心订单消息的消费延迟从每秒几十条开始持续放大半小时后堆积量超过百万而 Producer 端发送正常、Broker 端写入 TPS 没有下降。这个现象很典型写入没问题消费端出了状况。初始排查思路按照三层展开Consumer 实例本身、网络链路、Broker 存储读取。先看 Consumer 的监控大盘CPU 只有 15%内存正常再看 Broker 的读取 IO磁盘 util 在 5%~10% 徘徊。看起来一切都“正常”但消费就是不动这才是最诡异的地方。5.2 监控数据定位异常线程后来把 Consumer 的线程 dump 打下来发现大量消费线程阻塞在发送消息到下一个处理节点的等待上。也就是说消费回调里有一条发往另一个集群的异步调用链路它内部使用了固定大小的连接池而连接池被占满之后所有消费线程都在排队等待获取连接。顺着这个方向继续查发现该外部集群在高峰期出现 GC 暂停导致响应变慢平均 RT 从原来的 20ms 涨到 800ms。Consumer 的消费线程数虽然配的是 40但每个线程处理一条消息的时间从几十毫秒变成了一两秒整体消费 TPS 自然就跌到谷底。5.3 根因确认与参数调整根因不是 RocketMQ 本身而是下游依赖变慢。但 RocketMQ 消费线程模型放大了问题默认情况下一次消费取回的consumeMessageBatchMaxSize1每条消息处理都要等待一次下游往返下游慢多少倍消费就慢多少倍。调整策略分两步第一步把consumeMessageBatchMaxSize从 1 调大到 32一次拉取一批消息循环处理时至少能减少一半以上的上下文切换。第二步把消费逻辑改成批量提交到下游而不是逐条请求这样整体 RT 从峰值 800ms 降回 150ms 左右。同时给 Consumer 的线程池增加一个信号量保护机制当下游高延迟时让消费线程不再无限等待连接而是直接快速失败并重试避免线程被长时间占住。5.4 调优后的效果对比调整完成后消费延迟从峰值 1.2 亿条库存量开始快速回落大约 40 分钟追平到正常水位。下游恢复后消费 TPS 从原来的 2000 涨到 9000 以上CPU 占用从 15% 涨到 35%说明消费线程终于真正在干活而不是傻等 IO。这次排查给我最大的收获是RocketMQ 的消费性能调优不能只盯着 RocketMQ 自身的参数一定要把消费链路当做一个整体。消费线程阻塞在下游 IO 上是常见的隐形瓶颈而这个瓶颈在常规监控面板上几乎看不出来只有线程 dump 和链路追踪才能暴露。6. 常见问题速查表与避坑清单6.1 高频问题与排查方向一览现象大概率原因优先排查项写入 TPS 上不去磁盘 util 高同步刷盘 / 磁盘慢改为异步刷盘检查磁盘设备与 RAID消费堆积但不消费消费线程阻塞在下游线程 dump查看等待条件GC 频繁Young GC 长堆太小 / 对象分配太快调大堆内存避开 Page Cache 占用Full GC 导致毛刺堆溢出或显式 GC开-XX:DisableExplicitGC检查线程数Consumer 频繁 Rebalance实例数超过 Queue 数收敛实例数确认订阅关系一致发送延迟高TCP 缓冲区不足 / 跨机房检查网络 RTT调整 TCP 参数消息发送积压在 Producer客户端批量参数配置不当打开批量发送和压缩排查时建议按“客户端 → 网络 → Broker → 操作系统 → JVM”的顺序逐层排查而不是第一时间动 Broker 参数。我们团队还有一种习惯任何参数改动都先记录在案标注改动时间、改动值、预估影响调优前先快速 explain 一下“为什么改、改了会怎样”防止上线后出现问题时不知道源头。6.2 容易踩的坑和对应的预案坑一Broker 堆内存设置过大。堆 20GB、物理内存 32GB 的机器Page Cache 只有 12GB结果写入时频繁触发内核回收表现为延迟抖动。预案是堆控制在物理内存 1/3 以内优先保证 Page Cache 空间。坑二transientStorePoolEnabletrue但堆外内存没规划好导致堆外内存不够用而内存溢出。预案是开启该参数前加-XX:MaxDirectMemorySize显式设置堆外上限。坑三消费组内实例数和 Queue 数不匹配加机器反而降低消费速度。预案是在给某个消费组扩容前先确认该 Topic 的写队列数是否够。如果只有 8 个 Queue那最多 8 个实例在真正消费再加实例只能空转。坑四ONEWAY 发送方式误用于必须确认结果的场景。RocketMQ 的 ONEWAY 模式不等待响应只适合日志采集等允许丢失部分响应的场景如果用于对账可能造成消息发送失败无法感知。坑五压测数据和线上数据完全不一致。很多团队压测用 1KB 的纯文本消息线上却有大量 50KB 甚至 200KB 的消息二者对网络、磁盘和内存的压力模型完全不同。压测必须使用接近线上真实大小的消息体最好直接录制线上消息采样。6.3 调优好习惯基线化、小步改、可回滚调优不是一次性工程而是一个持续迭代的循环。我的个人习惯是每次只改一个变量。同时改了刷盘策略和 GC 参数出了问题根本不知道是哪个变量导致的。每次改动都跑一轮压测对照基线评测收益。如果改完 TPS 提升了 20%把这次的配置和压测结果一起记录下来形成配置版本库。每个参数改动都要评估回滚方案。比如把consumeMessageBatchMaxSize从 1 调到 64消费端内部逻辑是否支持批量处理如果中间有单条消息处理失败批量回滚会不会导致重复消费扩大这些都要提前想清楚。调优过程中保持“小步快跑”的节奏是最安全的。我曾经花了一个下午把十几个参数一次性全改完上线后立刻出问题回滚配置的时候整个人都是懵的因为根本不知道哪一项改坏了。后来把整套流程规范化“一次只动一个旋钮、对比基线、记录结果、必要时回滚”成了团队的调优铁律。做 RocketMQ 性能调优这些年我的体感是大部分性能问题都不是 RocketMQ 不行而是使用方式和资源配置与业务模型不匹配。很多人把调优等同于“修改 Broker 的几十个参数”但实际上真正收获最大的往往是这几件事把刷盘策略想清楚、把堆内存和 Page Cache 的关系摆正、把客户端的批量发送和消费线程配置调对、把消费链路上的外部依赖稳住。把这四件事做到位你的 RocketMQ 集群性能就已经超过了大部分团队。最后再分享一个小技巧压测调优时不要只看平均值一定要记录 P99 和 P999 延迟。RocketMQ 在 GC 周期内会有一小段“停顿”平均值看起来还行但 P999 可能已经从 50ms 飙到 500ms。用平均值调优会掩盖很多系统抖动问题只有把长尾延迟一起纳入优化目标线上真实体验才会真正稳下来。