做大数据流处理的人,迟早会撞上 Kafka。不管你是做实时数仓、日志采集,还是给推荐系统供特征,最终都会遇到同一件事:在业务和生产系统之间,需要一条能扛住高吞吐、还能把数据按顺序送到的通道,Kafka 就是这条通道的事实标准。这篇文章不是来讲概念的,而是想结合这几年在集群搬迁、监控排障、业务接入中实际踩过的坑,聊清楚 Kafka 在完整流处理链路里到底帮我们扛住了哪些事,以及要怎么把它用稳。
1. 流处理场景下,Kafka 到底承担了什么角色
1.1 没有中间层的流处理系统,会先把自己憋坏
回到最基础的场景:某个业务系统每分钟产生几万条订单事件,下游需要做实时指标计算、用户行为分析、数据入湖。如果让所有下游直接连业务库,靠定时任务拉增量,问题会很快暴露:业务库扛不住高频率扫描,表结构一变更所有采集脚本跟着改,新接入一个下游就得重新设计一套同步方案,中间只要有一个环节处理慢,整条链路就被拖死。
Kafka 解决的是“数据如何在多个系统间稳定流动”的问题。生产端把事件写入 Topic,消费端按自己的节奏从 Topic 里读取,彼此不需要知道对方的存在。业务系统只需要保证一条规则:把消息可靠地发到 Kafka。下游消费能力不足时,消息暂时堆积在 Kafka 里,不会反过来打垮业务;新增一个消费方时,只需要声明一个新的消费组从头或从指定位置消费,不需要上游做任何改动。这种“削峰填谷”和“一对多分发”能力,几乎是大数据场景的刚需。
在完整流处理架构里,Kafka 不是流计算引擎本身,但它是流计算的“数据交换机”。Flink、Spark Structured Streaming、Kafka Streams 这些任务通常从 Kafka Topic 读数据,计算结果再写回 Kafka Topic,下游再继续消费。没有这套统一的数据中转层,流计算作业的输入输出会变得极其难管理,重跑、追溯、多路复用都无从谈起。
1.2 同为消息队列,为什么实时链路里 Kafka 更常见
很多人会问,RabbitMQ、RocketMQ 不也是消息队列吗,为什么大数据流处理链路里默认选 Kafka?这里要分清场景。RabbitMQ 的强项是复杂路由、灵活交换机、延迟队列,适合业务系统内部模块间去耦合;RocketMQ 在事务消息、延迟消息、金融级可靠性上有大量设计;而 Kafka 从一开始就是为“海量日志、高吞吐、可回溯”设计的。
几个典型差异可以看下面的对比:
| 维度 | Kafka | RabbitMQ | RocketMQ |
|---|---|---|---|
| 模型 | Topic + 分区 + 消费组 | Exchange + Queue | Topic + Queue |
| 吞吐量 | 非常高,分区并行写读 | 中等,受单队列限制 | 高,天然支持分布式 |
| 消息回溯 | 按 offset 随机消费,支持重放 | 一般不支持随意回溯 | 支持按时间回溯 |
| 消费模式 | 拉模式,消费节奏可控 | 推拉结合 | 推拉结合 |
| 顺序性 | 分区内严格有序 | 单队列有序 | 队列内有序 |
| 生态 | 与 Flink/Spark/大数据组件集成最好 | 业务集成生态强 | 业务消息与削峰场景强 |
做大数据流处理时,最看重的是高吞吐、长时间数据保留、多消费组互不影响,这三条 Kafka 天然匹配。Kafka 的“拉模式”也很有优势:消费者按自己的吞吐能力去拉数据,不会出现 Brokers 推送过快把消费者打爆的情况,非常适合需要批量处理的数据管道。
1.3 可重放数据,才是 Kafka 撬动流处理的关键设计
有一个设计容易被忽略:Kafka 的消息不是消费完就被删除,而是根据保留策略在磁盘上存一段时间。这带来一个对流处理至关重要的能力——数据可以重放。
实际做实时任务时,经常发生这种情况:凌晨一点某个 Flink 作业因为代码 bug 崩溃了,等修复完已经过去一小时。如果消息系统是“消费完即焚”,错过的数据永远找不回来;而 Kafka 只要保留时间没到,就可以让新的消费者从最早 offset 或某个时间点重新消费这一小时的数据,先把延迟追回来,再决定是否需要修正结果。
另一个可重放价值的体现是多套流处理任务共用同一份数据。比如用户点击流进入 Kafka 后,实时风控消费一份,实时推荐消费一份,离线数仓晚上再从头消费一份做全量清洗。由于各消费组维护各自 offset,大家互不干扰。这种模式下,Kafka 实际承担了“缓冲层 + 总线 + 可回溯存储”三重职能,是流处理架构稳定性的一大保障。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 集群部署与规划,决定流处理后期能走多远
2.1 选 KRaft 还是 ZooKeeper,别等业务跑起来再纠结
如果你是这两年才开始搭 Kafka 集群,大概率会看到很多旧教程仍然在讲 ZooKeeper 模式。ZooKeeper 在早期 Kafka 里负责集群元数据、Controller 选举、Topic 配置管理,生产上用三台 ZK 是标配。这套架构的问题是:Kafka 本身依赖一套独立的分布式协调系统,运维组件多,Controller 发生故障时恢复速度不稳定,而且 ZK 里存储的元数据和 Kafka 实际状态可能出现不一致。
所以从 2.8 开始,Kafka 社区引入了 KRaft 模式,把元数据管理收回到 Kafka 自身,用 Raft 协议选主。3.x 版本之后 KRaft 逐步成熟,到了 4.x 版本 ZooKeeper 模式被彻底移除。对于新搭集群,我的建议很直接:直接上 KRaft,不要再逆着技术演进方向去部署 ZooKeeper 模式。
KRaft 的部署模式也灵活。小规模测试环境可以单节点同时跑 broker 和 controller 角色;生产环境如果条件允许,可以把 controller 节点单独拆出来,或者混合部署但控制 controller 数量为 1、3、5 这类奇数。需要注意的是,controller quorum 本身也有写入性能要求,不建议把 controller 和大量业务流量打到同一批节点上,否则 Controller 切换时可能因为负载过高而变慢。
2.2 集群规模、分区数和副本因子,要按流量估算而不是拍脑袋
很多团队搭 Kafka 集群时最常犯的错,是直接抄网上默认配置,三台机器建好就万事大吉。等业务流量上来,要么磁盘爆掉,要么分区数不够导致消费者并发卡死。这里给一个我自己常用的估算逻辑。
假设业务日志峰值 10 万条/秒,单条消息平均 1KB,那就是约 100MB/s 的写入流量。单台 Kafka Broker 用 SSD 的顺序写能力通常能到 300MB/s 以上,但不能只看这个数,还要考虑副本网络复制、读流量、磁盘保留空间。如果要保留 3 天,副本因子 3,光存储量就是:100MB/s × 86400 秒 × 3 天 × 3 副本,约 77TB。这还不算 Kafka 内部 Topic 和清理日志的临时空间,所以在容量规划上要留出至少 20% 余量。
分区数的估算不能盲目追求大。一个分区的正常吞吐和生产、消费能力有关,但从流处理场景看,分区数的上下界主要被两件事决定:一是 Kafka 本身能支撑的总分区规模,二是消费者并发数的需求。如果目标消费吞吐是 5 万条/秒,单个消费者线程稳定能处理 5000 条/秒,那么理论上需要 10 个分区才能扛住,再留一到两倍余量,可以定为 16 或 20 个分区。
副本因子方面,核心业务 Topic 我建议至少 3,日志类非关键 Topic 可以用 2。副本多确实能提高数据可靠性,但也会吃掉 Broker 的磁盘和网络带宽,所有 Topic 都设 3 副本并不一定划算。还有一个经验要记住:Topic 一旦创建,分区数虽然可以通过 kafka-topics.sh 增加,但增加之后不会自动把旧数据重新分布,需要做分区重分配才能均衡,操作成本不低,所以建 Topic 前最好做一次流量和并发预估。
2.3 Docker 部署无 ZK 的 Kafka,到底要配置哪些关键项
如果你想本地快速验证或做开发测试,用 Docker 部署单节点的 KRaft 模式 Kafka 很合适,不用装 ZooKeeper,一个容器就能拉起来。不同镜像的环境变量命名有差异,我以常见的 bitnami/kafka 为例,核心配置大概长这样:
bash复制docker run -d \
--name kafka-single \
-p 9092:9092 \
-e KAFKA_CFG_NODE_ID=1 \
-e KAFKA_CFG_PROCESS_ROLES=broker,controller \
-e KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093 \
-e KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092 \
-e KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=1@kafka:9093 \
-e KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER \
-e KAFKA_CFG_INTER_BROKER_LISTENER_NAME=PLAINTEXT \
bitnami/kafka:3.7
这里最重要的一个参数是 KAFKA_CFG_ADVERTISED_LISTENERS。它告诉客户端“你应该通过哪个地址来访问这台 Broker”。如果容器内客户端使用 localhost 连接,写成 PLAINTEXT://localhost:9092 没问题;一旦客户端在另一台机器上,就必须改成宿主机 IP 或域名,否则会遇到一个非常经典的报错:
code复制Error while fetching metadata with correlation id 4 : {topic=UNKNOWN_TOPIC_OR_PARTITION}
这类报错表面像 Topic 不存在,实际很多时候是客户端连上了某个 Broker,但拿到元数据后去连接真正的分区 Leader 时,发现 advertised 地址不可达。排查时要先确认 listeners 和 advertised.listeners 的区别,再去查网络和 hostname。
3. 生产者消费者接入,大数据流处理最常见的故障源
3.1 生产者配置不合理,数据会在你毫无感知时丢
我排查过不少“消费端偶尔少数据”的问题,最后都能在 Producer 参数上找到原因。默认情况下,如果一个 Java Producer 使用 acks=1,Leader 写入本地日志就算成功,数据是有可能丢的。如果要求高可靠,生产环境至少要设置:
properties复制acks=all
enable.idempotence=true
retries=2147483647
max.in.flight.requests.per.connection=5
delivery.timeout.ms=120000
compression.type=zstd
enable.idempotence=true 会为每个 Producer 分配 ProducerId,并给消息增加序列号,Broker 收到重复序列号时会去重,从而避免重试导致的重复写入。开启幂等之后,即使 max.in.flight.requests.per.connection 大于 1,也能保证分区内顺序不被打乱,因为 Broker 会校验序列号的连续性。
这里有个理解误区需要点出来:retries 并不能决定一条消息“最晚什么时候会放弃”,真正决定上限的是 delivery.timeout.ms。重试次数设得再大,一旦超过 delivery timeout,Producer 还是会抛异常。所以如果下游链路会长时间抖动,你需要调的是 timeout 而不是无脑加大 retries。
compression.type 在小数据量时容易被忽略,但大数据流处理中一定要重视。JSON 日志经过 zstd 压缩,普遍能省下 60% 以上的带宽和磁盘。压缩带来的 CPU 成本很低,大多数场景下收益远大于开销,尤其是多副本集群里,每省 1MB 流量等于省了 3MB 的副本复制流量。
3.2 Consumer 消费组与位移提交,是重复消费的根源
流处理数据的正确性,最终落在消费组和 offset 管理上。Kafka 消费组的存在让“多个消费者共同分担一个 Topic 的分区”成为可能,但同一个分区在同一个消费组内同一时刻只会被一个消费者实例持有。很多新手以为多开几个消费线程就一定能提高速度,忽略了分区数的上限:如果 Topic 只有 5 个分区,组内最多只有 5 个消费者真正在消费,多余实例只是空转。
自动提交位移默认是开启的,它每 5 秒提交一次当前消费位置。这个设计会带来两个问题:进程崩溃时,最近几秒已处理但未提交的消息会被重复消费;手动处理耗时较长时,上一次提交的位置可能已经远远落后,再发生 rebalance 时会出现大量重复。做大数据处理时,我更推荐手动提交,并且在业务处理成功之后再提交位移。
java复制while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(500));
for (ConsumerRecord<String, String> record : records) {
process(record); // 处理业务
}
consumer.commitSync(); // 处理完再提交
}
这里有个细节容易被忽略:很多人把“手动提交”理解成“随便找个位置 commit”,其实手动提交需要搭配“先处理成功再提交”的原则。如果先提交位移,再处理消息,处理中途崩溃,这批次大批量消息就再也不会上线消费,数据直接丢。手动提交还会带来另一个问题——处理时长太长会踢出消费组。消费者默认 max.poll.interval.ms 是 300000 毫秒,也就是 5 分钟,如果单批消息处理超过这个时间,即使消费者还活着,协调者也认为它卡死,触发 rebalance。
3.3 Spring Boot 和微服务接入,多套 Kafka 实例要独立管理
现在很多业务接入 Kafka 不走 Java 原生客户端,而是通过 Spring Boot,或者用 Go 的微服务框架。Spring Boot 的 spring.kafka.bootstrap-servers 配置确实很方便,但要注意:一个应用如果同时要对接两套 Kafka,不能把两个集群的地址都写进同一个 bootstrap-servers,因为客户端会从任意一个地址拉取整个集群的元数据,最后混乱地连到同一套集群上去。
正确做法是把不同 Kafka 集群当成完全隔离的客户端实例来管理:不同的 ConsumerFactory、ProducerFactory,每个实例有自己独立的 bootstrap-servers 配置。比如定义两个 KafkaTemplate Bean,分别注入到不同业务模块。示例大致是:
java复制@Configuration
public class MultiKafkaConfig {
@Bean("kafkaTemplateA")
public KafkaTemplate<String, String> kafkaTemplateA() {
Map<String, Object> props = new HashMap<>();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "kafka-a:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
return new KafkaTemplate<>(new DefaultKafkaProducerFactory<>(props));
}
@Bean("kafkaTemplateB")
public KafkaTemplate<String, String> kafkaTemplateB() {
Map<String, Object> props = new HashMap<>();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "kafka-b:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
return new KafkaTemplate<>(new DefaultKafkaProducerFactory<>(props));
}
}
在 Go 项目中使用 kafka-go 或 Sarama 时也是同样的思路:每个 Kafka 连接对象对应一套 broker 列表,不要让多个集群共用同一个 Client。还有一点容易踩坑:Go 的 kafka-go 里 Consumer Group 默认不会自动提交位移,需要你在处理完消息后显式调用 Commit(),否则会有大量重复消费。Java 看多了自动提交,切换到 Go 时特别容易忽略这一点。
4. 消息延迟高和积压问题,怎么一步步定位和调优
4.1 延迟高先分清是客户端慢,还是 Broker 慢
“Kafka 消息延迟高”是高频问题,但很多场景下 Kafka 本身是无辜的。Broker 的写入延迟通常只有几毫秒,端到端延迟高了,先要把链路拆开看:是生产端发送慢,还是消费端处理慢,还是 Broker 副本同步慢。
我的排查顺序通常是四步。第一步,先用命令行查消费组积压量:
bash复制kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
--describe --group my-stream-group
第二步,观察每个分区的 LAG 是否均匀。如果所有分区的 LAG 都增长,说明消费端整体处理能力不够;如果只有某个分区 LAG 很高,大概率是数据倾斜——同一个 key 的数据被哈希到了同一个分区,其中一个分区流量特别大,或者某个消费者线程卡住了。
第三步,进入消费者所在服务看日志和监控,确认单条消息的平均处理耗时。如果业务处理调用了外部接口,而外部接口快速变慢,消费线程会被阻塞,Kafka 侧自然看起来“延迟变高”,真正的问题却在下游。
第四步,如果消费端一切正常,再检查 Broker。磁盘 IO 是否打满、网络带宽是否饱和、副本是否长期处于 ISR 之外,这些都会拖慢消息的写入和拉取。
注意:不要一看到 LAG 大就去扩展消费者数量,如果分区数没有增加,加消费者不会提高并行度。分区数是消费者并行度的天花板。
4.2 吞吐量和低延迟之间的取舍,参数需要一套组合拳
Kafka 的很多参数都不是孤立的,Producer 的延迟优化和吞吐优化是矛盾关系。如果业务对实时性要求极高,可以把 linger.ms 调成 0,让消息立即发送;如果大数据量场景追求吞吐,建议用下面这组配置:
| 参数 | 建议值 | 说明 |
|---|---|---|
| linger.ms | 5~20 | 批量发送的等待时间,越大吞吐越高,延迟也越高 |
| batch.size | 32KB~64KB | 每个分区批次的大小,要结合消息大小调整 |
| compression.type | zstd | 降低网络带宽和磁盘占用 |
| buffer.memory | 64MB以上 | Producer 缓冲内存,太小容易阻塞 send |
| max.request.size | 1MB以上 | 单批次最大字节数,默认 1MB,大消息要调大 |
Consumer 侧的参数同样影响大。max.poll.records 决定一次 poll 返回多少条消息,如果每条记录处理时间较长,却把 max.poll.records 设成 5000,那么一轮 poll 的处理时间很可能超过 max.poll.interval.ms,从而被抛出消费组。我的经验是:处理耗时的任务,max.poll.records 调小,比如 500;处理快速的任务(比如只做转发),可以调大到几千。
fetch.max.wait.ms 和 fetch.min.bytes 也一样,前者是 Broker 等待数据的最长时间,后者是攒够多少字节才返回。实时性要求高,fetch.max.wait.ms 设 500;如果只是追积压或批量分析,可以设 5000,让 Broker 多攒一点数据再返回,减少网络交互次数。
4.3 积压时最忌讳“无脑加消费者”,正确做法要先看分区
遇到大规模积压,第一时间把消费组下的实例数加到十几台,这是最常见的处理方式,但如果没有提前扩展分区,效果会非常有限。假设 Topic 分区数为 20,消费组已经有 20 个消费者实例,后面再加多少台机器都只是空等,不会提高总消费速度。
处理积压的正确动作,先分场景。如果积压数据是日志、轨迹这类允许临时丢的数据,可以直接把消费位移重置到最新,让系统先恢复正常,再分析根因。如果积压的是订单、支付这类核心业务数据,就不能跳过,要优先确认下游系统有没有能力接收放量后的回放数据。很多积压的根源不是 Kafka 消费慢,而是下游数据库或外部接口的写入速度上不去,这时候硬扩消费者只会把更大了压力打到下游,反而弄垮数据库。
扩分区也是常见手段,但它不是无代价的。增加分区后,已有数据不会自动从旧分区迁移到新分区,要让积压数据快速被消费,需要先等旧分区数据消费完,消费组自然会有更多消费者去读新增的空分区。如果希望新分区立刻参与负载分担,需要做分区重分配,把那几个积压分区的数据迁移出去。这个过程在 Kafka 运维上叫 reassign,不建议在业务高峰期操作。
如果流处理任务本身用的是 Kafka Streams 或 Flink,还要注意作业内部并行度和 Topic 分区数的匹配。Source 端每个分区对应任务的一个并行子任务,如果作业并行度远小于分区数,源端读取会成为瓶颈;反之并行度大于分区数,部分算子线程又会空转。正确做法是让 Source 并行度接近或等于 Topic 分区数,后续 KeyBy 再根据业务需要增加并行度。
5. 实战速查:常见报错、监控指标与避坑心得
5.1 高频报错信息与排查方法速查
结合平时在社区和群里帮人看问题最多的几个方向,我整理了一张速查表。碰到类似报错时,可以照着这个方向去查,通常能省不少时间。
| 报错或现象 | 常见原因 | 解决办法 |
|---|---|---|
| Error while fetching metadata with correlation id 5 | 客户端无法解析 Topic 元数据,最常见是 advertised.listeners 配错 | 检查 broker 的 listeners 和 advertised.listeners,确保地址能被客户端访问 |
| cluster authorization failed | 客户端账号对这个 Topic 或消费组没有操作权限,ACL 配置缺失 | 检查 topic 读写权限和 group 读权限,使用 kafka-acls.sh 授权 |
| No service name defined in either JAAS or Kafka config | SASL 配置中缺少 serviceName,常见于 Kerberos 或某些客户端 | 在客户端配置中补充 security.protocol 和 sasl.kerberos.service.name |
| TimeoutException | 请求超时,可能网络分区、Broker 负载过高或 request.timeout.ms 太小 | 先看 Broker 和客户端网络,再调大 timeout 参数 |
| OffsetOutOfRangeException | 消费位移已过期被删除,或手动重置到了不存在的 offset | 确认保留时间,决定重置到 earliest 还是 latest |
| LeaderNotAvailableException | Topic 刚创建或分区 Leader 切换中 | 短暂等待后重试,若持续出现需检查副本状态和 ISR |
cluster authorization failed 这个报错我多说一句。很多场景不是权限真的没给,而是同一个客户端既读 Topic 又读消费组,ACL 只配了 Topic 却没有配 Group 的读权限。Kafka 的消费动作需要两组权限:对 Topic 的 Read 权限和对消费组的 Read 权限,缺一不可。写完第一次接入时,不妨把这两条规则都列出来对照检查。
5.2 可视化监控工具链怎么搭,重点看什么
每天看命令行查 LAG 终究不是长久之计,生产环境一定要有可视化监控。我自己用下来的方案是:Kafka UI 负责日常看 Topic、分区、消费组,Prometheus + JMX exporter + kafka_exporter 负责指标采集,Grafana 做大屏展示。
Kafka UI 这类开源工具对快速定位问题很有帮助,可以直观看到每个 Topic 的分区数、ISR 状态、消息量,还能查看消费组的 LAG 曲线。Offset Explorer(以前叫 Kafka Tool)则是桌面端利器,连接测试环境时很方便,适合快速写一条消息验证链路。
监控指标里,我认为最需要盯的是下面几类:
- 离线分区和副本状态:OnlinePartitionsCount、OfflinePartitionsCount、UnderReplicatedPartitions。Offline 或长期 UnderReplicated 都是严重隐患,必须立即处理。
- Broker 请求处理能力:RequestHandlerAvgIdlePercent 低于 30% 说明请求线程基本处于饱和状态,网络线程也一样。
- 消费组 LAG:按业务实时性分级设置阈值。核心支付链路 LAG 超过几千条就要报警,日志分析链路可以容忍更高。
- 网络和磁盘:BytesInPerSec、BytesOutPerSec 以及磁盘 IO 使用率。磁盘写满会导致 Broker 直接停止接收消息。
接入采集时,外置的 kafka_exporter 主要提供消费组 LAG 和部分 Topic 指标,Broker 内部的 JVM、请求线程指标还是需要通过 JMX exporter 或 JMX 端口获取。集群规模小的时候建议直接把 Broker 的 JMX 端口暴露给 Prometheus,规模大了再做安全加固。
5.3 这些年用 Kafka 攒下的几个避坑原则
第一个避坑原则:不要在业务高峰期做集群迁移或重启。Kafka 的重平衡和分区迁移都会产生额外流量,平时看起来很快的任务,一旦集群负载高,Controller 切换和副本同步可能会拖上很久。任何结构变更都先选低峰期,并且在变更前把消费组 LAG 降下来。
第二个原则:Topic 命名和容量规划一定要提前约定。不要放任业务方每个需求都新建 Topic,不然集群里会出现大量只有几 KB 消息量的小 Topic。Topic 数量越多,Controller 要管理的元数据就越多,Broker 启动和分区分配时的压力也越大。
第三个原则:保留时间不要盲目设置 7 天。保留时间越长,磁盘成本越高,而且一旦需要从很久之前回放,数据量可能大到无法处理。正确方式是按业务可容忍的故障恢复时间来确定,比如大多数实时链路允许回溯 1~3 天,就把核心 Topic 的 retention.ms 设成 72 小时或更短;离线分析需要更长时间窗口,可以把数据转存到对象存储而不是长期堆在 Kafka 里。
第四个原则:版本升级前一定要看兼容性。Kafka 的客户端和服务端都支持一定的协议兼容,但版本跨度太大时,还是会有各种奇怪问题,比如旧客户端无法识别新版 Broker 的元数据结构,或者新版的内部消息格式无法被旧客户端解析。我在一个项目里把 Broker 从 2.6 升到 3.5 时,少量老客户端就出现了连接不稳定,最后把客户端也统一升级才解决。
如果只让我留一条经验,我会说:把 Kafka 当成基础设施来对待,而不是一个“往里丢消息的中间件”。Topic 和分区设计、容量冗余、监控指标这些前期工作做得越扎实,后面流处理任务跑起来就越省心。毕竟 Flink、Spark 能算多快,很大程度上取决于 Kafka 能让它稳定地读到多少数据。
