Skip to content

Kafka 为什么吞吐量高

1. 核心结论

Kafka 吞吐量高,是依靠一整套围绕顺序读写、批量传输、零拷贝、分区并行、操作系统缓存设计出来的工程组合拳。

Kafka 做了一个非常明确的取舍:优先追求大规模数据流的持续吞吐能力,而不是单条消息的极致低延迟。因此,它在磁盘、网络、协议、消费模型上都尽量减少随机操作和额外拷贝。

2. 存储路径优化

2.1 顺序写磁盘比随机写快得多

Kafka 的消息写入方式是 append-only log,即消息不断追加到分区日志文件末尾,而不是在文件中间随机修改。

这种设计的价值非常大。传统认知里“磁盘慢”,很多时候慢的是随机 I/O,不是顺序 I/O。Kafka 把写入模式固定为顺序追加后,磁盘寻道成本显著下降,即使使用机械硬盘,也能获得很高的连续写入能力。

对比项 随机写 顺序写
磁盘寻道 极少
I/O 模式 离散 连续
吞吐量
Kafka 是否采用

2.2 利用操作系统 Page Cache

Kafka 并不是每来一条消息就立刻做一次物理落盘,而是先写入操作系统的 Page Cache

这样做有两个直接收益:

  • 用户态写入很快,减少了线程阻塞时间。

  • 操作系统会合并脏页,择机批量刷盘,提高整体 I/O 效率。

因此,Kafka 很多时候看起来像是在“写磁盘”,但实际热点路径更多是在和内核缓存打交道,这会让写入性能明显提升。

2.3 顺序读也能提升消费吞吐

Kafka 的消息不仅写入是顺序的,消费读取通常也是按偏移量递增顺序拉取的。

这意味着 Broker 在读数据时,同样更容易命中 Page Cache,并以连续方式读取日志段文件。生产端顺序写、消费端顺序读,形成了非常高效的数据路径。

3. 网络传输优化

3.1 批量发送减少系统调用次数

Kafka Producer 默认支持批量发送。它不会每产生一条消息就立即发一次网络请求,而是会根据 batch.sizelinger.ms 聚合多条消息后一起发送。

这会显著减少:

  • 网络包数量;

  • send / write 系统调用次数;

  • Broker 端请求解析次数。

单条消息很小时,批量机制带来的收益尤其明显。因为真正昂贵的往往不是消息体本身,而是每次请求固定存在的协议开销和上下文切换成本。

3.2 零拷贝降低 CPU 和内存开销

Kafka 在消息发送阶段会尽量使用零拷贝机制,例如 sendfile

传统发送流程通常是:磁盘数据读到内核态,再拷贝到用户态,再拷回内核态 Socket Buffer,最后发到网卡。Kafka 通过零拷贝减少了中间数据搬运次数。

这会带来两个结果:

  • CPU 更省,因为少了多次内存复制;

  • 网络发送更快,因为数据路径更短。

3.3 压缩提升“有效吞吐量”

Kafka 支持 gzipsnappylz4zstd 等压缩方式,而且通常是对批次压缩,不是对单条消息压缩。

批次压缩的好处是压缩比更高,单位网络带宽可以承载更多业务数据。虽然压缩会消耗一定 CPU,但在很多生产场景里,网络带宽比 CPU 更容易成为瓶颈,因此压缩通常能显著提升整体吞吐。

4. 分区并行模型

4.1 Topic 分区带来天然并行度

Kafka 的核心并行单位不是 Topic,而是 Partition。一个 Topic 可以拆成多个分区,不同分区可以分布在不同 Broker 上。

这意味着生产、存储、复制、消费都可以并行进行。吞吐量不再受限于单个文件或单台机器,而是可以随着分区数和 Broker 数线性扩展一部分。

4.2 Broker 级别水平扩展

Kafka 不是单机吞吐优化,而是分布式吞吐优化。当单机扛不住时,可以通过增加 Broker 节点,把更多分区迁移到新节点上。

所以 Kafka 的高吞吐不只是“单点快”,更重要的是“集群扩展后还能持续快”。

设计点 对吞吐量的作用
分区机制 提供并行写入与并行消费能力
多 Broker 部署 分摊网络、磁盘、CPU 压力
副本机制 在保证可用性的同时维持较高吞吐
消费组机制 支持多消费者并行拉取

4.3 生产者与消费者都能并行工作

生产者可以把不同 Key 的消息路由到不同分区,多个分区可以同时写。

消费者侧通过 Consumer Group 机制,让多个消费者实例分别消费不同分区。只要分区数足够,消费吞吐也能随着实例数增加而扩展。

5. 协议与消费模型优化

5.1 使用 Pull 模型更利于批量消费

Kafka Consumer 采用的是拉模型,即消费者主动调用 poll 去拉取消息,而不是 Broker 主动推送。

Pull 模型对吞吐量很友好,原因在于:

  • 消费者可以按自己的处理能力控制拉取节奏;

  • 一次可以拉取更多数据,天然适合批量;

  • Broker 不需要为每个消费者维护复杂的推送状态和背压逻辑。

因此,Kafka 更容易把“消费过程”做成稳定的大批量数据传输,而不是频繁的小消息推送。

5.2 简单存储结构降低 Broker 开销

Kafka Broker 不做复杂的消息索引和逐条确认管理,它的存储结构非常朴素:分区就是一个追加日志文件,再配合稀疏索引定位偏移量。

这种结构降低了 Broker 的元数据维护成本,也减少了磁盘和内存上的额外负担。系统越“简单直接”,极限吞吐往往越高。

5.3 顺序偏移量让定位成本可控

Kafka 每条消息在分区内都有递增的 offset,消费者依靠 offset 进行读取和提交。

相比一些需要复杂游标或状态机维护的消息系统,这种模型实现成本更低,Broker 端状态更轻,因而更容易支撑高并发、大流量场景。

6. 关键参数如何放大吞吐

6.1 Producer 侧关键参数

下面这几个参数,通常会直接影响吞吐量表现:

参数 作用 吞吐量影响
batch.size 单批次大小 越大越利于批量发送
linger.ms 等待聚合时间 适当增大可提升批量效果
compression.type 压缩算法 可提升有效网络吞吐
acks 确认级别 越严格吞吐通常越低
buffer.memory 生产端缓冲区大小 缓冲不足会限制发送能力

6.2 Broker 侧常见影响因素

Broker 侧虽然不需要频繁调参,但以下因素会直接影响吞吐上限:

  • 磁盘类型与磁盘队列深度;

  • 网卡带宽;

  • 分区数量是否合理;

  • 副本因子与 ISR 状态是否稳定;

  • JVM 堆大小是否合适,是否出现频繁 GC

6.3 Consumer 侧拉取参数

Consumer 想提高吞吐,核心思路也是“批量”:

props.put("max.poll.records", 500);         // 单次 poll 拉取更多消息
props.put("fetch.min.bytes", 1024 * 1024);  // Broker 累积到一定数据量再返回
props.put("fetch.max.wait.ms", 500);        // 等待更多数据凑批次

这些参数本质上都是在做同一件事:减少小而频繁的请求,提升单次传输的数据量。

7. 为什么 Kafka 看起来“用磁盘”,却依然很快

7.1 因为它避免了最慢的那部分磁盘操作

关键不在“是否用磁盘”,而在“怎么用磁盘”。Kafka 避免的是随机写、频繁刷盘、复杂索引更新这些昂贵操作,转而采用顺序追加和缓存写入,因此磁盘不再是想象中的那个瓶颈点。

7.2 因为热点数据常驻内存缓存

如果生产和消费速度都很高,最新日志段通常会停留在 Page Cache 中。

此时消费者读取消息时,很多请求实际上直接命中内存缓存,而不是重新访问物理磁盘。所以在高吞吐场景下,Kafka 的真实数据路径往往是“网络 → 内存 → 网络”,而不是“网络 → 慢磁盘 → 网络”。

8. 高吞吐的代价与边界

8.1 吞吐量高,不等于单条延迟最低

Kafka 通过批量、缓存、异步来提升吞吐,但这也意味着单条消息可能会多等几毫秒甚至更久。

因此,Kafka 很适合日志采集、埋点上报、流式管道、异步解耦,不一定适合要求极低尾延迟的强实时交易链路。

8.2 分区不是越多越好

分区增加可以提高并行度,但也会带来额外元数据、文件句柄、选举、调度成本。

所以分区数需要结合吞吐目标、消费者并行度、Broker 数量综合设计。分区过少,扩展性不足;分区过多,管理成本上升。

8.3 可靠性配置会影响吞吐

例如把 acks 设置为 all,并且副本因子较高时,消息确认链路会更长,吞吐通常会下降。

这不是 Kafka “变慢了”,而是系统在可靠性、可用性、吞吐量之间做取舍。面试中最好主动补一句,说明你理解的是工程权衡,而不是只会背优点。