做游戏饰品交易这几年,最常被同行问的一个问题是:你们这种垂直电商,凭什么敢在活动高峰期让几万人同时抢一件饰品?说句实话,光靠业务代码硬扛肯定扛不住,真正在底下兜底的是消息中间件。悠悠有品这个平台,核心交易链路全部走 RocketMQ,海量行为数据和日志管道全部走 Kafka,两个消息引擎各管一摊,才把高并发这块撑了起来。这篇文章不聊虚的,把我自己在选型、部署、调优和排障过程中积累的东西完整梳理一遍,给正在折腾 RocketMQ 和 Kafka 的朋友一份能直接抄作业的参考。
我自己也是从单机 RabbitMQ 一路踩坑踩过来的,RocketMQ 和 Kafka 这俩货看起来都是"发消息的",但脾气完全不同。RocketMQ 适合承载交易类场景,事务消息、延迟消息、顺序消息这些都是为业务量身定做的;Kafka 则天生适合做海量数据管道,吞吐量高、持久化强、生态庞大,Flink、Canal、ES 几乎都能跟它无缝对接。把这俩组合在一起用,不是炫技,而是业务逼着你这么干。
1. 业务场景与消息中间件选型考量
1.1 游戏饰品交易平台到底有什么特殊性
先说清楚业务场景,不然下面的架构决策看着就像空中楼阁。游戏饰品交易平台,核心业务是玩家之间买卖 CS:GO、Dota 2 这类游戏的皮肤、道具。听起来很简单,但实际做起来有几大痛点。
第一,高价值单品并发抢购。一个稀有皮肤可能价值几万块,上架瞬间会涌入大量买家请求,系统必须在极短时间内完成"冻结库存、创建订单、通知卖家、锁定饰品"这一串操作。任何一个环节出问题,轻则超卖,重则资损,这在游戏饰品交易里是不可接受的。
第二,交易链路长且状态多变。饰品交易不是简单的"买家付款卖家发货",中间涉及平台担保、API 发货、第三方机器人交割、超时自动关单、申诉仲裁等多个状态。每笔订单的状态流转都可能触发消息通知、库存变更、资金流水记录等一堆下游操作,这些操作天然需要异步化。
第三,海量埋点行为和日志数据。除了交易,用户浏览、搜索点击、价格曲线、风控行为等数据每时每刻都在产生,这些数据量远大于交易本身,需要进入数据仓库或搜索引擎供后续分析使用,但它们对实时性和事务性的要求很低。用一句话总结:交易数据要稳、要准、不能丢;行为数据要快、要大、能扩展。这两种诉求天然适合用不同的消息引擎来承接。
1.2 为什么核心交易必须交给 RocketMQ
先从交易链路说起。当初选型时,我们其实在 RabbitMQ、RocketMQ、Kafka 之间纠结了很久,但最终核心交易场景坚定地选了 RocketMQ。
最直接的原因是 RocketMQ 的事务消息。游戏饰品交易中,"创建订单 + 冻结饰品 + 扣减库存"这几个操作必须保持最终一致性。比如买家下单购买一件饰品,我们需要在本地事务里写入订单记录,然后发送一条"锁定饰品"的消息给库存服务,库存服务再通过消息确认冻结。如果消息发送和本地事务不同步,就会出现订单建了但饰品没锁住,或者饰品锁了但订单没建的情况,这在传统 MQ 里非常难处理。RocketMQ 的事务消息机制就是专门解决这个问题的:先发一条半消息,本地事务执行成功后再提交确认,Broker 才会把消息投递给消费者;如果本地事务不确定,Broker 还会主动回查,最终保证两边状态一致。这个能力 RabbitMQ 和 Kafka 原生都不具备。
另一个关键点是 RocketMQ 的延迟消息。交易场景里大量用到"超时关单""超时未支付自动取消""发货超时提醒"这类定时逻辑。RocketMQ 内置 18 个延迟级别,用一条消息就能实现 30 分钟超时关单,加一个 topic 就能把任务顺延,不需要额外引入一套定时任务系统。Kafka 在这块明显不如 RocketMQ 方便,它不支持延迟消息,要不自己用时间轮实现,要不依赖外部存储,复杂度会大不少。
再加上 RocketMQ 的消息语义更贴近业务开发者的思维模式,支持标签过滤、Key 索引、批量消息、Pull/Push 两种消费模式,在 Spring Boot 生态里用起来几乎无脑。社区又有阿里这么大一个使用方在背后,出问题的概率极低。所以核心交易链路,RocketMQ 是当之无愧的第一选择。
1.3 Kafka 负责的"海量数据"指的是什么
如果用一句话定位 Kafka 在我们平台里的角色,那就是"所有不要求强一致、只要求高吞吐的数据搬运工"。
实际上线后,Kafka 承担的职责大概有这么几块。第一块是用户行为埋点,浏览、点击、搜索、收藏这些事件数据,从 Nginx 网关和客户端上报过来后,先打入 Kafka,再由 Flink 实时消费写入 Elasticsearch 和 ClickHouse,供运营后台和推荐系统使用。第二块是业务日志与链路追踪,所有微服务的日志都通过 Kafka 汇聚后进 ELK,高峰期每秒几万条日志,只有 Kafka 能扛得住这个写入速度。第三块是 Canal 同步 MySQL Binlog 到其他存储,我们用 Canal 监听交易库的 Binlog,把变更事件发到 Kafka,下游消费者负责更新缓存、同步数据到数仓,这比用定时任务去扫表高效得多。
Kafka 在这类场景里展现出的优势非常明显:单分区顺序写磁盘,吞吐量轻轻松松上十万条每秒;分区机制天然支持水平扩展,流量涨了加分区就行;消息默认保留 7 天,即使下游临时故障,起来后也能从之前的 offset 接着消费,数据不容易丢。RocketMQ 也能做这些事,但它更适合复杂业务,在某些极端吞吐场景下不如 Kafka 纯粹,加上 Kafka 周边生态更加庞大,Flink、Spark、ES 这些组件对 Kafka 的支持都是第一优先级的,所以海量数据这块交给 Kafka 是完全合理的分工。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. RocketMQ 在核心交易链路的落地细节
2.1 事务消息:解决"下单锁饰品"的分布式一致性
这是 RocketMQ 在整个平台里最核心的价值点。我详细讲讲我们如何用事务消息完成"创建订单 + 锁定饰品 + 扣减库存"这个高频动作。
流程大概是这样的。用户在客户端点击购买,订单服务收到请求后,先向 RocketMQ Broker 发送一条 half message(半消息),这条消息此时对消费者不可见。半消息发送成功后,订单服务执行本地事务:往订单表插入一条状态为"待支付"的订单记录,同时修改库存表的字段,把对应的饰品 ID 标记为"冻结中"。本地事务如果提交成功,订单服务向 Broker 发送 commit 指令,这条半消息才会变成正常消息,库存服务才能消费到,然后执行真正的库存预占和卖家通知。如果本地事务执行失败,就发送 rollback 指令,Broker 会删除这条半消息,整个链路相当于什么都没发生。
这里有一个很关键的问题:如果本地事务执行完了,但 commit 指令因为网络抖动没发出去怎么办?RocketMQ 的解决方案是回查机制。Broker 会定期扫描那些长期处于"半消息"状态的消息,主动向订单服务发起回查请求,询问"你本地事务到底成没成?"订单服务根据订单号去查本地数据库,如果订单存在就返回 commit,不存在就返回 rollback。依靠这个机制,消息和业务数据的最终一致性就稳了。
实操中有两个细节值得注意。第一,回查接口必须实现幂等,因为 Broker 可能同时发起多次回查,被查询的订单状态可能已经变更,不能重复更新。第二,事务消息的本地事务执行时间不宜过长,不然会频繁触发回查,白白增加 Broker 和业务方的压力。凡是消费端收到事务消息后需要查库的,记得给数据库查询加索引,否则一旦业务量上来,回查接口也跟着变慢,连锁反应非常难受。
2.2 延迟消息:超时关单与自动发货的定时利器
交易平台里大量任务天然具有"延迟执行"的需求。买家拍下饰品后如果 30 分钟没付款,订单要自动取消并释放库存;卖家确认发货后如果买家一直不点确认收货,48 小时后要自动打款给卖家。这些需求如果用定时任务去扫表,会非常消耗数据库资源,尤其是表数据量大时,频繁全表扫描根本不是办法。RocketMQ 的延迟消息在这时就派上了大用场。
RocketMQ 的延迟消息用法很简单。发送消息时设置一个延迟级别,消息不会立刻被消费者拉到,而是等到延迟时间过了之后才变为可消费状态。默认支持 18 个级别,分别对应 1s、5s、10s、30s、1m、2m、3m、4m、5m、6m、7m、8m、9m、10m、20m、30m、1h、2h。我们在超时关单场景里,就直接用第 16 个级别,也就是 30 分钟,订单创建成功后发一条延迟消息,消费者收到后先判断订单当前状态,如果仍然是"待支付"就执行关单,否则直接忽略。整个过程不需要额外部署定时任务调度中心,依赖少、延迟准、代码也干净。
但这里有个进阶玩法值得提一下。如果需要的延迟时间不是这 18 个级别怎么办?我在项目里采用的方法是"业务时间戳 + 消费者主动判断"。发送消息时不依赖 Broker 的延迟级别,而是消息带上业务目标时间(比如"应该在 3 小时 17 分后处理"),消费者收到后把消息重新投递到内部延迟队列,或者直接塞进 Redis ZSet 按时间排序,用一个单独的消费者去轮询到期任务。这种方式灵活度极高,任何延迟时长都能支持,适合 RocketMQ 默认级别覆盖不到的场景。
之后我们在生产环境专门为延迟消息做了监控,统计每个延迟级别对应的积压数量和消费耗时。因为延迟消息在生产端看不出来"迟到"了多久,一旦时间偏差超过预期,业务就会出现订单关闭过早或过晚的问题。监控项里核心看"延迟消息从生产到消费的时间差"和"延迟队列积压数量"这两个指标,实测下来非常管用。
2.3 控制台部署与集群模式选择
RocketMQ 本身部署不算难,但对于第一次上手的朋友,我还是建议用 Docker 先快速搭一套本地环境跑通流程,再慢慢深入。比较推荐的方式是 apache/rocketmq 官方镜像,启动 NameServer 和 Broker 各一个容器,再单独装一个 rocketmq-dashboard 控制台用于可视化查看消息和队列状态。Docker 安装时要注意给 Broker 映射三个端口:9876 是 NameServer 端口,10909 是 Broker 主端口,10911 是 HA 端口。很多人在容器里启动后本地连不上,多半就是端口映射没配全。
控制台这里我多说一句。很多新手不明白 dashboard 在排查问题时的价值,实际上它是 RocketMQ 运维的第一窗口。你可以在控制台上直接查看某个 topic 的队列数量、生产消费速率、消费者组堆积情况;可以按 Message Key 或时间范围精确查消息内容;还可以直接查看消费失败的消息和重试队列。出问题时,90% 的情况先用控制台看一眼堆积量和消费组状态,定位方向基本就有数了。如果你是想自己打包 dashboard,遇到 SSL peer shut down 之类的报错,通常是 Maven 拉取依赖时证书问题,换成阿里云镜像源,再把 Maven 版本和 JDK 版本对齐就能解决。
生产环境不建议单机部署,至少要做成主从架构,条件允许直接上 Dledger 模式实现自动选主与故障切换。Dledger 模式跑起来后,即使某一台 Broker 挂了,消息也能从另外几台节点上继续读取,整体可用性大幅提升。而且它支持自动主从切换和故障恢复,不需要人工干预,运维成本反而比手动主从低。我们线上就是三组 Dledger,每组三节点,按交易链路的不同 topic 做了分组隔离,效果非常稳定。
3. Kafka 驱动海量数据的实操要点
3.1 埋点日志与数据管道的真实结构
Kafka 在我们平台里的数据管道,画出来可能觉得很简单,但实际跑起来要考虑的问题远不止"装个 Kafka、发消息、收消息"这么简单。我拿最典型的"行为埋点 → 实时数仓"链路举例。
客户端上报的行为事件(点击、浏览、下单意向等)先打到统一接入层,接入层直接写入 Kafka 的 user-behavior-topic,这个 topic 我们设置了 12 个分区,生产端按用户 ID 做 Key 哈希,保证同一个用户的行为消息进同一个分区,这样下游按用户维度聚合时的顺序性就非常好处理。Flink 任务消费这个 topic,实时做清洗和维度补全,然后写入 Elasticsearch 供运营后台实时查询,同时把明细数据落一份到 ClickHouse。这套链路上游高峰期每秒能涌入几万条事件,Kafka 在里面扮演的就是一个"零丢失、可回放、高吞吐"的缓冲池,把生产和消费之间的速度差异完全解耦了。
另外一条非常重要的管道是 Canal 监听 MySQL Binlog。交易库里订单表、库存表的数据变更,通过 Canal 实时解析 Binlog,把变更记录序列化成 JSON 发到 Kafka 的 canal-binlog-topic。下游有几个消费者:一个负责更新 Redis 中的库存缓存,一个负责同步数据到数仓,还有一个负责把订单变更事件推给实时风控系统。这套设计避免了下游系统各自去扫数据库的尴尬,数据的一致性也全靠 Kafka 分区有序这个特性来保证。
这里我要特别说一个很多人容易踩的坑:Kafka 的 topic 一定要提前规划好分区数和保留策略。最理想的是上线前根据峰值流量估算好分区数,不要事后频繁扩容。因为分区数虽然可以动态增加,但扩容后旧数据还在旧分区里,想要全局有序就必须以 Key 为单位重新分布,这个代价非常大。我们自己在埋点场景一开始就规划了 24 个分区,比峰值需要的数量还多预留了 50% 的余量,之后流量翻了快一倍也没再动过分区数。
3.2 生产端与消费端的核心参数调优
Kafka 连接 Spring Boot 的配置在网上到处都是,但真正上线后决定稳不稳的,往往是几个细节参数。我不打算贴完整配置,只挑几个对稳定性和性能影响最大的参数说说。
生产端最重要的三个参数是 acks、linger.ms 和 batch.size。acks=all 虽然会牺牲一点吞吐量,但能保证 leader 和所有 ISR 副本都写入成功后才返回,对交易类数据这是底线。linger.ms 设置了发送线程等待更多消息加入批次的时间,默认是 0,每条消息都单独发,吞吐很吃亏;我们设置为 5ms,配合 batch.size 调到 64KB,单个 topic 的生产吞吐量直接翻了几倍。还有一个参数是 compression.type,生产端开启 snappy 压缩后,带宽和磁盘占用都有明显下降,CPU 开销增加得很少,非常划算。
消费端最容易出问题的参数是 max.poll.records 和 max.poll.interval.ms。Kafka 的消费模型是消费者拉取一批消息,然后业务代码处理这批消息,处理完后调用 poll 提交 offset 并拉取下一批。整个"处理 + 提交"必须在 max.poll.interval.ms 时间内完成,否则消费者会被误判为失效,触发 rebalance。我们曾经在某个消费者里写了一段往 ES 批量写入的逻辑,单条消息处理时间偏长,结果线上频繁触发 rebalance,消费速率骤降,消息堆积一度超过 2000 万条。后来把 max.poll.records 从 500 调到 100,同时给 ES 写入加了批量缓冲,问题才彻底解决。如果业务中确实需要对消息做耗时的外部调用,我建议用异步线程池,让 poll 线程保持空转,这样既不超时又能提高吞吐。
3.3 可视化工具与本地环境连接
Kafka 虽然没有官方控制台,但生态里好用的可视化工具不少。我们团队日常用得最多的是 Offset Explorer,旧版本叫 Kafka Tool。它最大的价值是能直观看到集群里每个 topic 的分区数据、消费组的 offset 情况、消息内容和积压量,排查"消费组为啥没消费了"这类问题非常高效。
有些人连接本地单机 Kafka 时老连不上,这里有一个容易忽略的点:Offset Explorer 连接时填的默认值通常是对的,但你必须在 Advanced(高级) 配置里确认一下安全协议是不是 SASL_PLAINTEXT 或者 SSL 格式,如果本地 Kafka 没开认证,PLAINTEXT 是首选,另一个常见原因是 Kafka 服务端配置的 listeners 地址和客户端连接的地址不一致。比如容器里启动的 Kafka,客户端用 localhost 连,但容器内 listeners 绑定的是容器 IP,这种情况只需要把 Kafka 的 advertised.listeners 配置成 PLAINTEXT://localhost:9092 就能解决。我遇到至少三次类似的连接问题,全是这个原因。
另外提一句,如果你是长期在 IDEA 里做开发,装一个 Kafka 相关插件也能提升效率。插件能直接在编辑器里查看 topic、发送消息、查看消费组状态,调试代码时比来回切工具方便很多。
3.4 没有 Zookeeper 的 Kafka?KRaft 模式集群安装
以前部署 Kafka 集群,必须先搭一套 Zookeeper,不少新手在这里就被劝退了。好在从 Kafka 3.3 版本开始,KRaft 模式正式进入稳定阶段,Kafka 可以完全脱离 Zookeeper 独立运行,用内置的元数据日志(Metadata Log)来管理集群元数据。Docker 部署更加方便,启动命令里不再需要配置 Zookeeper 相关环境变量,只需要指定唯一的集群 ID,然后分别启动 controller 和 broker 两个角色即可。
如果你用 Docker Compose 搭建 KRaft 模式的 Kafka,有几个细节值得提醒。第一,KAFKA_CFG_NODE_ID 和 KAFKA_CFG_CONTROLLER_QUORUM_VOTERS 必须严格一致,所有节点要能互相通信;第二,KAFKA_KRAFT_CLUSTER_ID 需要集群内统一,你可以用 kafka-storage random-uuid 命令生成一个,各节点共用;第三,KRaft 模式下 controller 节点在元数据变更时也承担一部分写入压力,所以至少保证三节点以上才能做到高可用。我们测试环境一开始用单节点跑得很欢,后来模拟 controller 宕机测试,才发现单节点模式下整个集群都会不可用,误以为 Kafka 出故障了。
至于"Kafka 如何延迟 30 分钟消费"这个问题,归根到底是因为 Kafka 没有原生的延迟消息能力。社区里最通用的做法有两种:一种是在生产端给消息加一个业务过期时间,消费者拉到后先判断时间是否满足,不满足就把消息重新塞回一个"缓冲 topic",同时用 Thread.sleep 或者定时器控制暂停时间,但这个方案在小规模场景下还能接受,大规模时必须配合分层 topic 来做;更通用的做法是构建一层延迟消息编排模块,用 Redis ZSet 按到期时间排序,到期后再把消息转投到真正的业务 topic。如果想快速实现,也可以直接利用 Kafka 的 KafkaConsumer 在 poll 时传入超时参数做变通,但业务复杂以后逐渐会变得很难维护。
4. 双引擎架构的踩坑实录与排查技巧
4.1 消息幂等:重复消费是常态
不管是 RocketMQ 还是 Kafka,消息的"at-least-once"语义决定了消费端必须自己做幂等处理。也就是说,同一条消息可能会被投递多次,消费方要保证处理结果是唯一的,不能因为重复消费导致订单被重复创建、库存被重复扣减。
以交易场景为例,我们在所有涉及资金和库存的消费者里都强制要求"业务唯一键去重"。具体做法是:消费消息时,先从消息体里取出订单号,去 Redis 里查 order_process:{orderNo} 这个 key 是否存在,不存在就建立并设置一个过期时间,继续执行业务逻辑;存在则直接返回不处理。这相当于用 Redis 做了一版"分布式锁 + 幂等表"的组合。另一种更稳妥的做法是数据库层面加唯一索引,比如订单状态流水表以 order_no + status 作为联合唯一索引,重复插入直接报 Duplicate Key,业务层捕获后按幂等处理。两种方案可以同时上,不同消费者根据场景选择一种即可。
有人问双引擎下是不是对 RocketMQ 和 Kafka 要用不同的幂等策略,我的经验是机制完全一样,区别只在你对消息可靠性的心理预期不同。Kafka 的消费端默认是拉取后自动提交 offset,如果业务处理失败但 offset 已经提交,这条消息就丢了;我们统一改成手动提交,并且在业务处理成功后再提交 offset,这样至少保证"处理失败能重试",配合幂等机制才能做到真正的不丢不重。
4.2 顺序消息怎么保证
交易场景里有一个比较经典的顺序需求:同一笔订单的"创建 → 支付 → 发货 → 完成"事件必须按顺序被下游处理,否则就可能出现"订单还没创建就先收到支付成功通知"这种荒谬情况。RocketMQ 和 Kafka 都支持顺序消息,但用法差异很大。
RocketMQ 的顺序消息分两种。全局顺序,整个 topic 的所有消息都要按顺序消费,这个性能损耗太大,只适合数据量极小的场景;更常用的是分区顺序,发送消息时通过实现 MessageQueueSelector,根据订单号哈希选择同一个 MessageQueue,消费者队列单线程消费,这样同一个订单的消息就天然有序。我们这边所有交易事件都按 orderNo 做 Key 路由,保证一个订单的所有消息进入同一个队列,下游消费者再配合状态机校验,即使消息乱序到达也能通过状态判断是否丢弃还是暂存,双保险。
Kafka 的顺序性则依赖于分区。发送消息时把订单号作为 Key,同一个 Key 的消息会进入同一个分区,分区内的消息是按写入顺序存储的,消费者在单分区内读取自然有序。但要注意,一旦消费组内开启了多线程消费,顺序依然会被打破,所以顺序消息要求对应分区的消费者必须单线程,或者自己在业务侧实现"按 Key 分组串行处理"。我们在 Kafka 的消费端就用了一个 ConcurrentHashMap<订单号, 锁>,每个订单的后续事件都尝试获取自己对应的锁,保证同一订单的处理串行化,效果很好。
4.3 消费堆积与速率控制
消费堆积是双引擎架构里最高频的问题,没有之一。RocketMQ 和 Kafka 的堆积原因五花八门,常见于下游依赖变慢、消费者逻辑有 bug、分区数不够、消费参数不合理等。应对堆积的思路基本一样:先查堆积量,再查消费速率,最后定位是生产太快还是消费太慢。
RocketMQ 的控制台可以直接看到每个消费组在每个队列的堆积数量。如果堆积持续上涨,第一反应不是去加消费者实例,而是先看消费者的日志里有没有大量重试和异常。很多堆积其实是消费者里某个外部调用超时导致的,比如调用第三方接口或查询数据库变慢,这时优先解决下游性能问题,而不是盲目扩容。我们做游戏饰品交易时,因为库存接口偶尔抖动,导致消息消费速率骤降,后来在消费端加了熔断降级:外部接口失败时直接降级为查缓存,或者把消息暂时投递到重试队列,整体消费速率立刻恢复正常。
Kafka 这边还有一个特色问题是消费速率受 fetch.min.bytes 和 fetch.max.wait.ms 控制。如果消费速率远低于生产速率,可以适当调大 fetch.min.bytes 到 1KB 以上,让消费者每次拉取带回更多数据,减少网络往返次数;同时调大 max.poll.records 到 500 以上。但要注意,max.poll.records 调大后处理时间也会变长,必须同步调整 max.poll.interval.ms,否则又会触发 rebalance。另外,RocketMQ 支持给消费者设置 ConsumeThreadMin 和 ConsumeThreadMax,通过调整线程数也能直接改变消费速率,Kafka 则需要靠增加消费者实例或分区数来横向扩容。
我还想提醒一个细节:不要把所有消息都用一个消费者组处理。比如"订单事件"和"行为埋点"是两种完全不同的消费模式和速率要求,如果共用一个消费者组,数据流量互相干扰,出问题时排查也非常困难。我们的做法是每个业务域独立 topic、独立消费者组,避免相互影响。
4.4 高频报错排查速查表
最后这部分我给一张速查表,全是我们在实际运维中碰到的真实问题和解决方案,希望对你有帮助。
| 报错现象 | 可能原因 | 排查方法 | 解决思路 |
|---|---|---|---|
Error while fetching metadata with correlation id |
客户端无法连接 Kafka Broker,常见于 listeners 地址错误或网络不通 | 检查 advertised.listeners 配置、端口连通性 |
把 listeners 设置为对客户端可访问的地址,检查防火墙 |
cluster authorization failed |
客户端权限不足,ACL 配置有误 | 检查 Kafka 的 ACL 配置和客户端 sasl.jaas.config |
给对应的 consumer group 和 topic 授权,确认用户名密码 |
| RocketMQ 消费者收不到消息 | 消费者组订阅关系不一致,或 tag 过滤不匹配 | 控制台查看消费者组的订阅配置,对比生产端 tag | 统一 tag,确认所有消费者订阅同一个 topic 和 tag |
| Kafka 消费组 rebalance 频繁 | max.poll.interval.ms 超时,或 session.timeout 过小 |
查看日志中 rebalance 触发频率和各消费者处理耗时 | 调大 max.poll.interval.ms,优化消息处理速度,适当增加消费者实例 |
| RocketMQ 消息大量堆积且消费速率很低 | 消费者逻辑有阻塞,或消费线程数不足 | 查看消费者线程池状态、堆栈,检查外部调用耗时 | 定位阻塞点,调整 ConsumeThreadMin/Max |
| 本地连不上 Kafka 可视化工具 | Bootstrap Server 配置错误或安全协议不匹配 | 确认 Kafka 服务和工具在同一个网络,检查协议类型 | 修正 advertised.listeners,在工具高级配置里选择正确协议 |
RocketMQ dashboard 打包报 SSL peer shut down |
Maven 仓库拉取依赖时证书错误 | 查看 Maven 日志确认具体依赖下载失败 | 切换阿里云镜像源,重试构建 |
| Kafka 部分分区消费延迟极高 | 分区分配不均,某个消费者处理能力弱 | 查看各消费者实例的 partition 分配和消费速率 | 增加消费者实例,或者按业务 Key 重新设计分区策略 |
| 消费端手动提交 offset 失败 | 消费组已经 rebalance,提交时 group 已变化 | 检查是否在 poll 循环外提交,或提交延迟太久 | 保证在当前 poll 周期内完成提交,设置合理 auto.commit 策略 |
这里特别强调一下,遇到 Kafka 的 cluster authorization failed,大部分时候不是用户名密码输错了,而是客户端没有给消费组授权。Kafka 的 ACL 不仅要授权 topic 的读写,还要授权消费组本身,很多人漏掉了 --group 这个参数,导致权限永远验不过。RocketMQ 那边则相对省心,默认 ACL 门槛不高,但一旦你开启了 ACL,记得生产者、消费者、dashboard 的配置都要同步更新,漏掉一个就全线失败。
写在最后的实战心得
其实做架构最深的体会是:不要迷信任何中间件,要按业务的真实诉求来选。RocketMQ 交易稳,Kafka 吞吐强,把这个组合用好了,前面提到的所有问题基本都是可以提前预防的。我个人最推荐你优先把监控体系搭起来,RocketMQ 控制台和 Kafka 的消费组监控、堆积告警、消费耗时告警一定要配全,很多线上事故都是"先有告警后有感知",而大多数告警都是消息堆积触发出来的。等到你把消费堆积、幂等、顺序、权限这些基础问题都趟过一遍,后面整个平台就稳了。
最后再分享一个小技巧:如果你在 Spring Boot 里同时使用 RocketMQ 和 Kafka,建议把两个引擎的消费者线程池大小、消费超时时间、重试机制参数单独配置,不要图省事共用一套。因为交易消息必须快速响应但不能重试太多,行为数据则可以慢一点但要保证吞吐。分开调配之后,线上出问题时定位起来真的快很多。
