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.size 和 linger.ms 聚合多条消息后一起发送。
这会显著减少:
-
网络包数量;
-
send/write系统调用次数; -
Broker 端请求解析次数。
单条消息很小时,批量机制带来的收益尤其明显。因为真正昂贵的往往不是消息体本身,而是每次请求固定存在的协议开销和上下文切换成本。
3.2 零拷贝降低 CPU 和内存开销
Kafka 在消息发送阶段会尽量使用零拷贝机制,例如 sendfile。
传统发送流程通常是:磁盘数据读到内核态,再拷贝到用户态,再拷回内核态 Socket Buffer,最后发到网卡。Kafka 通过零拷贝减少了中间数据搬运次数。
这会带来两个结果:
-
CPU 更省,因为少了多次内存复制;
-
网络发送更快,因为数据路径更短。
3.3 压缩提升“有效吞吐量”
Kafka 支持 gzip、snappy、lz4、zstd 等压缩方式,而且通常是对批次压缩,不是对单条消息压缩。
批次压缩的好处是压缩比更高,单位网络带宽可以承载更多业务数据。虽然压缩会消耗一定 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 “变慢了”,而是系统在可靠性、可用性、吞吐量之间做取舍。面试中最好主动补一句,说明你理解的是工程权衡,而不是只会背优点。