Kafka 为什么分区越多越慢?NVMe 时代它的“顺序写”优势反而成了坑

🔑 关键词:Kafka分区越多越慢,NVMe,顺序写,Kafka性能,消息中间件选型

📖 摘要:一次 NVMe 集群压测引发的思考:Kafka 的分区模型是机械硬盘时代的妥协。对比 Pulsar 和 Redpanda,结合真实迁移事故,给出新项目选型建议和避坑步骤。

三个月前,我被拉去帮朋友调一个 Kafka 集群,压测死活上不去。 机器是云厂商最新一代 NVMe,三个 broker,三副本。 分区数开到了 96,但他手里只有一个流,50 个消费者线程。 这配置按任何一份 Kafka 调优指南都该有百万吞吐,可峰值卡在 80 万 records/s。 延迟尾巴从 200ms 飙到 3s,ISR 一直在收缩,他看着监控面板问我:分区还不够吗?

图片

我说,不是不够,是多了。 NVMe 普及之后,Kafka 设计时赖以生存的“随机 IO 灾难”已经不存在了。 老机械盘顺序写能到 150MB/s,随机 4K 写只有几百 KB/s,你当然只能顺序写。 而一块 NVMe 的随机 4K 写可以到 20 万 IOPS,换算出来超过 800MB/s,和你顺序写的差距不是数量级了。 Kafka 为了顺序写,把“分区”设计成最小并行粒度,同一个分区在 broker 端的日志追加始终是串行的。 于是你想要吞吐就得加分区,加分区又带来成百上千的 segment 文件。 分区到 200 以上,消费组协调和元数据推送都会开始出现周期性的长尾,吞吐没上去,cpu system 先爆了。

图片

这本质上不是调优问题,是模型债。 Kafka 的分区顺序性不是为多个消费者设计的,更像为了方便自己维护 offset 而做出来的一条单车道。 同一条车道上的所有消息都必须排队,一旦某个 key 的流量把分区打满,你没有任何办法把这个分区分拆到其他 broker 上。 你以为可以通过增加分区扩容,但生产环境改分区数等于把整个 topic 的顺序性、消费位移和窗口状态全部打碎。 真跑到线上你会发现,扩容的操作时间比你省下来的那点吞吐要多得多。

图片

当时我把 topic 从 48 个分区扩到 96 做测试,吞吐没有提升。 但 cpu system 从 20% 涨到 28%,消费者端 GC 暂停从 47ms 变成 160ms。 这只说明了一件事:Kafka 的并发能力被锁在分区维度,加线程不加分区,线程会空转;加分区不加线程,元数据和协调开销先吃掉收益。 传统经验喜欢拿 broker 核数乘四当分区数建议,那是当年为了限制单个分区的写线程数、减少随机 IO 才提出来的公式。 现在新硬件把“随机”变成了常规操作,这条公式也就失去了存在前提。

图片

市面上和 Kafka 对比的 Pulsar 和 Redpanda,我都不觉得解决了本质问题。 Pulsar 把存储拆到 BookKeeper,broker 无状态了,所以单个 topic 可以做到几万个分区而不被本地磁盘拖垮,但它多了一套 BookKeeper 要运维,跨机房配置难度不降反升。 Redpanda 用 Raft 把每个分区绑定到一个 Seastar 线程里,单分区性能确实干净,可仍然是一段有序日志的模型。 当你某个分区成为热点,照样会在单核上软锁死,只是从 Kafka 的锁竞争换成了 CPU 占用率爆满。 这种换汤不换药的结构,让我更怀疑“分区顺序日志”是不是被市场高估了。

图片

有一个我经历过的迁移事故,比理论更有说服力。 之前我们一个三节点集群换硬件,用 Kafka 自带的副本迁移把数据搬去新机器。 迁移跑到一半发现 ISR 缩了,查了下原因,是新机器挂的是 SATA 固态而不是 NVMe,搬迁速度赶不上生产日志的追加速度。 Kafka 的迁移本质上是一个持续追读生产者位点的长队列复制,每个分区都只能单拉单写,没有快照也没有并行分片。 200GB 的分区就是一条 200GB 的长队列,任何一点抖动都会让落后距离越拉越大,后来我们被迫把 retention 压到 6 小时,丢了一部分历史数据才搬完。 这个痛点让我开始理解 Pulsar 做分段存储的好处:数据是短的 chunk,可以像数据库页一样被独立移动。

图片

所以我不会劝你放弃 Kafka,毕竟周围生态是它的安全区。 但如果你要在新项目上选型,并且打算用 NVMe,那应该重新评估那些默认设置。 我的建议是,分区数不要按核数倍数来定,而按消费者的实际并行度来定,比如消费者并发是 60,topic 做 200 个分区就是在供养一堆你看不见的协调开销。 还要时刻盯 ISR 收缩而不是只看 lag,lag 只是表象,ISR 收缩才是死前的信号。 至于 Kafka 的 Tiered Storage,我在 3.9 版本上试过,远程读冷数据的设计仍然很绕,不太适合直接上生产。 在你按下“启用”键之前,先想想:真的需要那一条全局不串位的日志吗? 我的排序是:三个节点以内跑 Kafka,五千分区以上别碰 Kafka,中间地带如果没有专职运维,不如去忍受 Pulsar 的复杂。 这也许是给 Kafka 最好的告别——不是背叛,而是不再把它当神话。