上周消息队列圈子出了个大新闻,Kafka生态中最主要的商业化厂商被整合收购了。虽然开源项目本身还在,社区也还在运转,但任何一个靠消息总线活着的人,心里都很难不咯噔一下。
说白了,Kafka这些年早就不只是一个消息队列,而是很多企业的“数据中枢神经”。从微服务异步调用、业务事件广播,到日志采集、流式计算,几乎全都挂在它上面。现在这个中枢神经背后的商业主体换了东家,接下来会发生什么?谁都不敢打包票。
这篇文章我想认真聊一聊,在Kafka生态出现不确定性之后,做技术选型的人该怎么评估替代方案,已经跑在Kafka上的系统又该怎么考虑迁移。内容适合正在做中间件选型的架构师、负责消息基础设施的运维同事,以及被“要不要换”这个问题反复折磨的技术负责人。我不会回避具体操作层面的细节,也不会只讲概念和趋势,尽量把评估、迁移、避坑的完整链条都拆开讲清楚。
1. 生态震荡真正影响到的,不只是“换个软件”
很多人第一反应是:Kafka开源项目还在,代码还是那套代码,被收购就一定要换吗?这个问题的答案,要比表面复杂得多。
1.1 Kafka为什么这么难替代
Kafka在企业里承担的角色,早就超出了“队列”两个字能概括的范围。
从功能定位看,它至少同时干了三件事。第一件事是消息管道,微服务之间异步通信、削峰填谷,靠的是它。第二件事是日志中枢,Nginx访问日志、业务操作日志、数据库变更日志,统统灌进去再做分发。第三件事是事件存储,Kafka允许你回放数据、重新消费,这个“可回溯”特性让它成了一个轻量级的事件仓库,很多实时数仓、流计算作业都直接把它当数据源。
这种三位一体的角色,意味着你在评估替代方案的时候,不能只问“哪个产品吞吐量高”,还要问:它能当存储用吗?它的消息能回放多久?它的分区机制和消费组机制能匹配现有业务吗?
生态绑定也是个大问题。Kafka不是孤零零一个Broker在跑,它周围长出了一整圈配套工具。连接器体系负责和几十种数据源对接,流处理库让你直接在消息流上做聚合计算,Schema Registry管理消息格式,还有各种监控工具、跨机房复制工具。这套生态不是一两天长出来的,而是过去近十年一点一滴积累起来的。
所以“换掉Kafka”的本质,是把一张密密麻麻的依赖网整个重新织一遍。这不是软件替换,这是架构重组。
1.2 被收购后,哪些变化是真实风险
收购消息出来之后,团队内部讨论最多的是三类风险。
第一类是授权风险。开源项目的License可能调整,这个不用我多说,行业里已经发生过多次“开源变商用”的事件。虽然Kafka本身的代码大概率继续开放,但周边商业组件的边界怎么划,以及社区版和商业版的功能差异会不会拉大,都是未知数。对企业来说,未知本身就是成本。
第二类是社区治理风险。项目的主要维护者如果从社区独立转向公司主导,那么特性演进的方向就会优先考虑公司商业利益,而不是社区共识。典型后果是:某些你等了很久的功能永远不做,某些你不想要的变更反而被强推。
第三类是价格与服务风险。商业化支持服务涨价、服务条款变更、技术人员流失,这些都会直接影响生产系统的稳定性保障水平。
这三种风险不会在收购当天爆发,而是会在一两年内慢慢释放。正因为是“慢变量”,很多运维团队才容易麻痹大意,等到真的被坑了再补救,成本就完全不一样了。
1.3 受影响最大的四类系统
基于我平时接触到的项目,受影响最大的是下面四类。
第一类是数据管道类系统,包括日志采集、数仓入湖、实时同步。这类系统对消息队列的依赖最深,一旦Kafka侧出现稳定性问题或者API不兼容,整个数据链路直接断掉。
第二类是事件驱动型微服务系统。订单状态变更、支付回调、库存扣减,这种业务对消息的一致性和顺序性要求极高,迁移复杂度最高。
第三类是流计算系统。Kafka Streams、Flink这类作业通常直连Kafka,换了底层消息引擎,作业代码、检查点、幂等机制都可能要改。
第四类是跨机房/多集群同步系统。用Kafka原生的复制机制做异地多活的团队,迁移时还要额外考虑同步链路的重建。
这四类系统里,第一类最容易替换,第三类最难替换。后面的迁移方案我也会按这个难度梯度来展开。
2. 替代方案全景:先看清选项,再做决定
在聊具体替代方案之前,我想先说一个原则:不要因为“某某方案最近很火”就动手换。消息队列是基础设施里的基础设施,选错了,后面几年都在还债。
2.1 四条技术路线的真实画像
目前主流的替代路线大致有四条,每条路线的性格差异非常大。
第一条路线是同类分布式消息系统,代表性项目是Pulsar和RocketMQ。这类系统和Kafka定位最接近:同样支持分区、副本、消费组,同样能扛高吞吐,同样具备消息持久化能力。Pulsar的特色是存储和计算分离,Broker不存数据,数据放在底层分布式存储里,扩容时可以做到“计算节点无状态”。RocketMQ则是经过电商大促场景反复锤炼的产物,事务消息和延迟消息支持很成熟。
第二条路线是传统消息代理,代表是RabbitMQ。它的优势是路由灵活,支持交换机、绑定、死信队列等高级路由玩法,对复杂业务消息场景非常友好。但是单机吞吐量比Kafka低一个量级,消息堆积能力也弱,更适合中小规模系统。
第三条路线是轻量级事件总线,比如NATS JetStream。这类产品的核心理念是“轻”,性能很好、运维简单,但生态和应用范围窄,适合丢给内部系统做简单的发布订阅,不适合支撑大规模数据管道。
第四条路线是Kafka协议兼容引擎。也就是你在不换SDK、不换客户端、不换连接器的情况下,把Broker端换成一个宣称兼容Kafka协议的新引擎。这类方案迁移成本最低,但兼容性永远不会是百分百的,一些边缘行为在压力下会露馅。
2.2 选型决策矩阵:用分数替代理智争吵
团队讨论选型最容易陷入“谁嗓门大听谁的”僵局。我的做法是拉一张加权评分表,让所有人把标准摆到桌面上。
这张表的核心评价维度有七个:吞吐量、消息堆积能力、延迟表现、数据一致性保证、协议与生态兼容性、运维复杂度、社区活跃度。
以我之前做过的一个模拟项目X为例,当时的业务场景是日均亿级消息、峰值几十万TPS、消费端有严格顺序要求、团队规模只有三个人。七个维度的权重分配是:吞吐25%、堆积20%、一致性20%、生态15%、运维10%、延迟5%、社区5%。
对照组分别是Pulsar、RocketMQ和Kafka协议兼容引擎。结果如下:
- 吞吐量:三者都达标,Kafka兼容引擎稍微略逊,因为底层实现需要兼顾兼容性。
- 堆积能力:Pulsar和RocketMQ最强,可以把积压数据持久化到存储层,兼容引擎较弱。
- 一致性:Pulsar支持严格顺序和幂等生产;RocketMQ的顺序消息需要特定编码规范;兼容引擎延续Kafka语义,但要打问号。
- 生态兼容:Pulsar提供Kafka兼容协议插件,RocketMQ需要完全换客户端,兼容引擎最省事。
- 运维复杂度:Pulsar组件多,部署重;RocketMQ相对轻;兼容引擎最接近原Kafka运维习惯。
最终那个项目选了RocketMQ。因为团队小、对顺序消息需求强、日常运维能力有限,而流量规模并没有到非Pulsar不可的程度。这告诉我们一个道理:最好的方案不是“参数最强的”,而是“团队能长期驾驭的”。
2.3 为什么我不建议“谁火用谁”
很多人选型有一个坏习惯:谁近期火、谁在大会上被讲得多,就倾向选谁。这在大厂也许问题不大,毕竟有充足人力兜底,但在中小团队是完全不同的游戏规则。
选型的本质是选“维护成本曲线”。新项目、新框架带来的学习成本、踩坑成本、招聘成本,都是隐形的。举例来说,Pulsar架构很漂亮,但一个由三个维护者组成的团队要去扛住BookKeeper集群、ZK元数据、Broker三套组件的日常运维,压力是很大的。而RabbitMQ虽然简单,但真要支撑每秒几十万消息的堆积,又需要相当精细的调优。
我在实际评估中会给团队一个很务实的建议:在满足业务性能红线的前提下,优先选团队已经有经验积累、社区故障案例丰富、工具链齐全的方案。踩坑的人足够多,反而是个安全信号,因为大多数坑都已经被前人填平了。
3. 从评估到迁移:一份可以照着抄的落地流程
评估做完之后,最怕的就是“决定换了,但不知道第一步干嘛”。迁移一个承载核心业务的Kafka集群,一定是分阶段、可回退的,绝不存在“一夜切换”的浪漫操作。
3.1 存量盘点:先画一张完整的依赖图
动手迁移之前,必须把Kafka周边的东西全部盘点清楚。我建议至少整理五张表。
第一张表是Topic清单。每个Topic的名称、分区数、副本数、保留时间、消息量级、生产者和消费者归属哪个应用,全部列出来。
第二张表是消费组清单。每个消费组在消费哪些Topic、当前Lag水位、消费频率如何、是否有关键业务依赖。
第三张表是客户端依赖清单。团队里有多少种语言在用Kafka客户端?Java、Go、Python、Node.js全都要盘出来,因为不同语言的SDK对替代系统的适配程度不一样。
第四张表是生态组件清单。有哪些连接器在跑、有没有人用流处理库、有没有Schema Registry、监控告警是怎么接的。
第五张表是拓扑依赖图。哪些系统是上游生产者、哪些是下游消费者、哪些链路是强一致性的、哪些允许短暂延迟。这张图是整个迁移方案的作战地图。
五张表做完之后,你就会发现“换消息队列”这件事的真正边界。它往往比想象的宽,也比想象的可控。
3.2 数据迁移:全量+增量双写是正经解法
迁移数据这件事,千万不能只导一次数据就完了。因为Kafka本质是一个持续流动的管道,数据永远在产生,没有“静态快照”一说。所以标准做法是全量加增量双写。
全量阶段,把旧集群的历史消息按Topic和分区顺序回放一遍到新集群。这个阶段不用追求追平,把水底数据先搬过去。增量阶段,让新旧集群在业务高峰期共同运行,生产端同时对两个集群发送消息。这里要注意:双写如果做成强同步,会影响生产耗时,所以通常做成异步双写,允许秒级延迟。
消费者侧的切换也有讲究。等增量双写跑一段时间,确认新集群完全追平、Lag归零之后,再把消费者切到新集群。但是这里有一个容易忽略的问题:消费位点不一致。老消费者在老集群消费到的位置,未必对应新集群的相同位置。假如双写的时候同一个事件被写入了两次,消费者切换后可能重复消费的一部分数据。所以所有下游消费者必须先保证消费幂等,才能做切换。
我的经验是:双写观察期至少要持续一到两个业务高峰周期。比如日常有“月末结算”这种高峰期,就一定等到高压力日过完之后再做切换。切忌挑业务低峰期切换,因为低峰期验证不了多少东西。
3.3 客户端兼容性:能用协议插件就别急着改代码
不同替代方案在客户端改造上有天壤之别。
最省事儿的是Kafka协议兼容引擎,完全复用现有客户端和连接器,代码一行不用改,改的是Broker端配置和连接地址。听着很美好,但要注意:不要被“协议兼容”四个字冲昏头脑。协议兼容不代表行为兼容,一些边缘场景比如特殊的分区分配策略、压缩算法、事务边界,在重负载下可能会暴露出兼容缺口。所以即使走这条路,也必须做一轮完整的压力测试,尤其是探针级别的场景测试。
其次是Pulsar这种带Kafka兼容插件的系统。你可以直接使用Kafka客户端,但Pulsar侧协议转换层会带来一些吞吐损耗。如果你追求极致的性能,还是建议改用Pulsar原生客户端,但那就意味着所有应用代码都要重新适配。
最麻烦的是RocketMQ这类完全不兼容Kafka协议的系统。所有生产者和消费者都要换SDK,消息序列化格式、重试机制、消费位点管理方式全都不一样。这也是为什么我坚持“迁移评估阶段先盘点语言清单”的原因——如果团队里主要是Java业务,换RocketMQ还能接受;如果一半是Java一半是Python,那改造工作量就会瞬间膨胀。
3.4 灰度切换三步法:每一步都要能回头
我自己总结的切换流程就三步:影子验证、分流观察、正式切流。
影子验证阶段,新集群接收生产环境的真实流量拷贝,但不影响生产消费。这个阶段主要验证:新集群在高压力下是否稳定、消费者逻辑在新系统上是否兼容、消息有没有异常丢失或重复。
分流观察阶段,把一部分业务切到新集群上,比如先切一个内部系统、低风险业务。观察指标包括生产耗时、消费Lag、Broker负载、错误率。这里有一个小技巧:切完之后,把新旧两套集群的监控指标并排放在同一个大屏上对比,一眼就能看出差异。
正式切流阶段,也不是一次性全切。按业务域逐步切换,每切完一个域,至少稳定运行24小时再切下一个。同时把旧集群的流量降到最低但不立即销毁,保留完整的回滚能力。我见过太多团队“切完就拆旧集群”,结果新集群出了严重问题,又因为旧集群数据已经清掉而追悔莫及。低成本的回滚保障,永远是生产环境的底线。
4. 迁移实战中的高频坑:每一个我都踩过
迁移过程中踩坑几乎不可避免,但有些坑是可以提前预判的。下面这些问题是真实生产环境里我反复遇到过、并且最终整理进团队手册的内容。
4.1 数据链路类问题
最经典的是消费乱序。Kafka能在分区内保证有序,但替代系统未必。比如RocketMQ,普通消费模式天然不保证全局顺序,要保证顺序必须用顺序消息模式,并且要把需要有序的消息发送到同一个队列。这个如果没设计好,订单流程里“先创建后支付”的消息可能变成“先支付后创建”,业务直接乱掉。
再比如重复消费。Kafka消费端在重平衡、宕机重启时本来就会产生一些重复消息,很多团队在Kafka时代靠“下游幂等”兜底。换到新系统后,重复消费的概率会重新洗牌,比如某些系统在消息回放、集群切换过程中会放大重复概率。迁移前必须把所有消费者的幂等性重新检查一遍,不能假设“原来没事现在也没事”。
还有一个容易忽略的问题:消息大小限制。Kafka默认单条消息的限制值是1MB,有些替代系统对这个参数的默认值或行为定义不一样。曾经有团队迁过去之后,因为一条大报文消息连续提交失败,消费者甚至没看到任何报错,整条链路静默卡住,排查了很久。迁移前一定要压测验证大消息场景。
4.2 客户端行为类问题
不同客户端SDK对“发送超时”“重试次数”的处理逻辑差异很大。Kafka Java客户端在配置了重试之后,消息发不出去时会阻塞还是异步重试,有一套复杂的语义。新系统的SDK未必遵循同样的语义,迁移后可能出现生产端qps暴涨、重试风暴、甚至消息重复投递。
消费端的重平衡机制也值得警惕。Kafka的重平衡是消费者组内协调,新系统可能是另一个实现逻辑。在频繁上线、扩容缩容的场景下,新系统重平衡的时间可能更长,导致消费停顿时间变长。
我曾在一个模拟项目中遇到一个新系统的消费组在几个消费者实例之间“来回拉扯”的情况,消费速率极其不稳定,一会儿冲到最高,一会儿归零。最后排查下来是新系统的分区分配算法和Kafka的RangeAssignor存在差异,需要显式指定分配策略才能恢复。这种坑,没经历过的人真的想不到。
4.3 运维监控类问题
迁移之后最容易被忽视的是监控系统本身。Kafka有一套成熟的监控指标体系:消息积压量、消费速率、Broker网络吞吐、磁盘使用率。这些指标在替代系统上的名称、含义、采集方式可能完全不同。比如某系统里“消费积压”的概念就没有一个现成指标,需要自己通过“最大偏移量-当前消费位点”间接计算。如果监控没跟上,就相当于闭着眼睛开车。
磁盘容量规划也要重新做。Kafka的日志分段、清理策略和Compaction机制,很多替代系统虽然有类似功能,但实际磁盘占用曲线不一样。我见过迁移后磁盘占用快速增长、两天之内把磁盘写满的例子——不是新系统有Bug,而是新系统默认的日志保留策略和Kafka不同,需要单独配置调整。
还有网络布局。新系统可能对跨机房的带宽占用更敏感,比如某些系统内部组件之间通信频繁,导致机房专线流量暴涨。有条件的话,先在小规模流量下专门观察跨机房流量再决定是否全面铺开。
4.4 常见问题速查表
为了在日常排障时方便查阅,我把高频问题整理成了一个表格。
| 问题现象 | 可能原因 | 排查思路 |
|---|---|---|
| 消费并发了,但总Lag不降 | 消费者处理逻辑出现阻塞,或分区分配不均 | 查看消费者线程状态、单分区消费速率,先确认分区分配算法是否合理 |
| 生产端偶尔报超时,但数据没丢 | 客户端重试机制或网络抖动导致 | 对比新旧集群生产耗时曲线,检查网络小包丢包率 |
| 消费端出现大量重复消息 | 切换过程中位点不一致或幂等机制缺失 | 检查消费位点重置逻辑,确认下游是否全部具备幂等 |
| 消息顺序错乱 | 顺序消息编码不规范或队列路由策略不对 | 确认发送端是否按顺序键指定队列,检查消费端并发线程数 |
| 磁盘占用暴涨 | 日志保留策略配置不一致或清理线程异常 | 检查保留时间和清理策略配置,观察清理线程日志 |
| 跨机房同步延迟增大 | 新系统同步机制对带宽更敏感 | 专门监测跨机房流量和同步延迟曲线,必要时调整批处理参数 |
| 切换新系统后CPU不稳定 | 客户端协议转换开销或GC配置不当 | 拆分场景压测,定位是协议转换层问题还是业务代码问题 |
这张表不是万能的,但大部分现场问题都能顺着这个思路找到突破口。核心原则还是那句:出了问题先怀疑“新旧行为差异”,再怀疑业务代码,排查效率会高很多。
5. 迁移完成之后,还有三件事要做
很多人以为切换完就算结束了,其实真正的长期工作才刚刚开始。
第一件事是重建容量基线。新系统的性能基线不能沿用旧集群的参数。团队需要重新做一轮完整的压测,把生产、消费、堆积、恢复四种场景都跑一遍,形成新的容量规划文档。并且要把这个基线作为日常监控的告警依据,而不是等到线上出问题再猜。
第二件事是完善故障演练。消息队列最怕的不是峰值流量,而是突发故障下的恢复能力。我会建议团队定期做“拔盘演练”:人为停掉一个Broker节点、断掉一条网络链路,看看消费链路的恢复时间、消息是否有积压告警、重平衡是否顺利。这类演练在Kafka时代就该做,换了新系统之后更要重新做一遍。
第三件事是知识库沉淀。每个团队折腾完一次迁移,都会积累大量“血泪教训”。如果没有沉淀下来,下次遇到同样问题又要重新踩一遍。迁移项目最后一定要留出专门的人力,把架构决策、配置清单、排障经验整理成内部文档,这是我认为整个项目里性价比最高的一项投入。
踩过几次坑之后,我现在给团队的建议一直很简单:不要神化任何技术,也不要被某次行业事件牵着鼻子走。消息队列是基础设施,它最重要的属性永远是稳定、可控、可维护。在Kafka生态的变动面前,认真做一轮评估、梳理清楚自己的依赖、准备好兜底方案,比急着“赶潮流”换新系统要重要得多。技术选型的核心,永远不是选一个别人说好的东西,而是选一个让你自己睡得着觉的东西。