mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
2090 字
6 分钟
为什么 Kafka 这么快
2024-01-23

Kafka 是分布式消息队列中的性能怪兽。单机可达百万级 TPS,延迟控制在毫秒级别。Kafka 是怎么做到的?答案藏在对操作系统和硬件特性的极致利用中:顺序写、零拷贝、页缓存、批量处理。这些看似简单的技术,组合起来创造了 Kafka 的性能神话。

一、Kafka 的性能数据#

1.1 真实世界的性能表现#

LinkedIn 的 Kafka 集群性能数据:

指标数值说明
单机吞吐量100 万+ TPS3 台 broker 集群
端到端延迟2-5 ms同数据中心
单分区写入速率30-50 MB/s普通 SSD
消息大小1KB 左右最优大消息需压缩
数据保留时间7 天 - 永久可配置

1.2 与其他消息队列的对比#

xychart-beta title "消息队列吞吐量对比(单机,TPS)" x-axis ["Kafka", "RocketMQ", "RabbitMQ", "ActiveMQ"] y-axis "吞吐量(万/秒)" 0 --> 120 bar [100, 50, 5, 1]
消息队列单机吞吐量延迟适用场景
Kafka100 万 TPS2-5 ms日志、大数据流
RocketMQ50 万 TPS1-5 ms金融、电商交易
RabbitMQ5 万 TPS1-10 ms业务解耦、异步任务
ActiveMQ1 万 TPS10-50 ms传统企业集成

二、顺序写 vs 随机写#

2.1 磁盘 IO 的性能鸿沟#

Kafka 的高性能源于一个核心设计:只做顺序写,避免随机写

flowchart LR subgraph 顺序写 S1[磁头] --> S2[磁道 1] S2 --> S3[磁道 2] S3 --> S4[磁道 3] Note over S1,S4: 磁头不移动,持续写入 end subgraph 随机写 R1[磁头] --> R2[磁道 100] R2 --> R3[磁道 5] R3 --> R4[磁道 200] Note over R1,R4: 磁头频繁寻道 end

2.2 性能差异的量级#

IO 类型机械硬盘SSD性能差距
顺序读100-200 MB/s500 MB/s基准
顺序写100-200 MB/s500 MB/s基准
随机读0.5-2 MB/s50 MB/s10-100x
随机写0.5-2 MB/s30 MB/s10-100x
# 磁盘性能的数学分析
sequential_write = 150 # MB/s, 顺序写
random_write = 1.5 # MB/s, 随机写
# 顺序写比随机写快多少倍?
speedup = sequential_write / random_write
print(f"顺序写比随机写快 {speedup:.0f} 倍")
# 输出:顺序写比随机写快 100 倍

2.3 Kafka 的追加写策略#

Kafka 的消息存储采用 Append-Only Log 结构:

flowchart TB subgraph Kafka 分区日志 M1[消息 1] --> M2[消息 2] M2 --> M3[消息 3] M3 --> M4[消息 4] M4 --> M5[...] end W[生产者写入] -->|追加到末尾| M5 R1[消费者 1] -->|顺序读| M1 R2[消费者 2] -->|顺序读| M3 Note over M1,M5: 每个分区是一个有序日志<br/>新消息只能追加,不能修改

这种设计的优势:

特性优势
只追加避免随机写,顺序 IO
不可变无需加锁,并发安全
顺序读预读友好,缓存命中率高
简单无需复杂索引结构,维护成本低

2.4 磁盘顺序写的硬件原理#

为什么顺序写这么快?从硬件角度看:

flowchart LR subgraph 机械硬盘 HDD H1[磁头定位] --> H2[盘片旋转] H2 --> H3[数据传输] Note over H1,H3: 随机写:磁头频繁移动<br/>顺序写:磁头几乎不动 end subgraph 固态硬盘 SSD S1[FTL 映射] --> S2[页写入] S2 --> S3[块擦除] Note over S1,S3: 顺序写减少 GC 开销<br/>随机写触发写放大 end

机械硬盘:寻道时间约 5-10ms,占随机 IO 总时间的 90% 以上。顺序写几乎零寻道。

SSD:顺序写减少 FTL(Flash Translation Layer)的映射开销,降低写放大。

三、零拷贝技术#

3.1 传统数据传输的拷贝次数#

传统的网络数据传输需要 4 次拷贝、4 次上下文切换:

sequenceDiagram participant App as 应用程序 participant Kernel as 内核 participant Disk as 磁盘 participant NIC as 网卡 Note over App,NIC: 传统方式:4 次拷贝,4 次切换 Disk->>Kernel: DMA 拷贝到内核缓冲区 Kernel->>App: CPU 拷贝到用户缓冲区 App->>Kernel: CPU 拷贝到 Socket 缓冲区 Kernel->>NIC: DMA 拷贝到网卡 Note over App: 两次上下文切换(读) Note over App: 两次上下文切换(写)

3.2 零拷贝:sendfile#

Kafka 使用 Linux 的 sendfile 系统调用,实现零拷贝:

sequenceDiagram participant App as 应用程序 participant Kernel as 内核 participant Disk as 磁盘 participant NIC as 网卡 Note over App,NIC: 零拷贝:2 次拷贝,2 次切换 Disk->>Kernel: DMA 拷贝到内核缓冲区 Kernel->>NIC: DMA 拷贝到网卡 Note over App: 数据不经过用户空间<br/>减少 2 次 CPU 拷贝
// 零拷贝的代码示例
// 传统方式
read(file_fd, buf, len); // 内核 → 用户
write(socket_fd, buf, len); // 用户 → 内核
// 零拷贝方式
sendfile(socket_fd, file_fd, offset, len);
// 数据直接从文件缓冲区到网卡,不经过用户空间

3.3 mmap:内存映射文件#

Kafka 也使用 mmap 实现消费者读取:

flowchart TB subgraph 用户空间 App[应用程序] end subgraph 内核空间 PageCache[页缓存] end subgraph 磁盘 File[日志文件] end File -->|DMA| PageCache PageCache <-->|mmap 映射| App Note over App,File: mmap 将文件映射到进程地址空间<br/>读写文件如同读写内存
技术拷贝次数上下文切换适用场景
传统读写4 次4 次需要修改数据
sendfile2 次2 次文件到网络传输
mmap2 次可能更少随机访问文件

3.4 Kafka 的零拷贝应用#

flowchart LR subgraph 生产者 P[Producer] end subgraph Broker PC[页缓存] Log[磁盘日志] end subgraph 消费者 C1[Consumer 1] C2[Consumer 2] end P -->|网络传输| PC PC -->|异步刷盘| Log Log -->|sendfile| C1 Log -->|sendfile| C2 Note over PC: 写入利用页缓存<br/>读取利用零拷贝

四、批量发送与压缩#

4.1 批量发送的威力#

Kafka 将多条消息打包成一个批次发送:

flowchart LR subgraph 生产者 M1[消息 1] M2[消息 2] M3[消息 3] M4[消息 4] end subgraph 批次 B[Batch<br/>包含多条消息] end subgraph Broker K[Kafka] end M1 --> B M2 --> B M3 --> B M4 --> B B -->|一次网络请求| K Note over B: 减少网络往返<br/>提高吞吐量

批量发送的性能提升:

# 批量发送 vs 单条发送
message_count = 10000
message_size = 1024 # 1KB
# 单条发送:每条消息一次网络请求
single_requests = message_count
single_overhead = single_requests * 0.1 # 假设每次请求 0.1ms 开销
# 批量发送:100 条消息一个批次
batch_size = 100
batch_count = message_count / batch_size
batch_overhead = batch_count * 0.1
print(f"单条发送开销:{single_overhead:.0f} ms")
print(f"批量发送开销:{batch_overhead:.0f} ms")
print(f"性能提升:{single_overhead/batch_overhead:.0f} 倍")
# 输出:
# 单条发送开销:1000 ms
# 批量发送开销:10 ms
# 性能提升:100 倍

4.2 消息压缩#

Kafka 支持多种压缩算法,在批次级别压缩:

压缩算法压缩比压缩速度解压速度适用场景
none1.0x最快最快低延迟场景
gzip2-3x带宽受限
snappy1.5-2x平衡选择
lz41.5-2x最快最快高吞吐场景
zstd2-3x中等中等高压缩比需求
// Kafka 生产者配置压缩
Properties props = new Properties();
props.put("compression.type", "lz4"); // 启用 LZ4 压缩
props.put("batch.size", 16384); // 批次大小 16KB
props.put("linger.ms", 5); // 等待 5ms 凑批次

4.3 批次大小对性能的影响#

xychart-beta title "批次大小 vs 吞吐量" x-axis ["1KB", "16KB", "64KB", "256KB", "1MB"] y-axis "吞吐量(万 TPS)" 0 --> 120 bar [20, 60, 90, 100, 95]

批次大小需要权衡吞吐量和延迟:

批次大小吞吐量延迟适用场景
实时系统
离线处理
中等平衡平衡大多数场景

五、分区并行处理#

5.1 分区的分布式设计#

Kafka 的 Topic 被划分为多个 Partition,分布在不同 Broker 上:

flowchart TB subgraph Kafka Cluster subgraph Broker 1 P0[Partition 0<br/>Leader] P3[Partition 3<br/>Follower] end subgraph Broker 2 P1[Partition 1<br/>Leader] P4[Partition 4<br/>Follower] end subgraph Broker 3 P2[Partition 2<br/>Leader] P5[Partition 5<br/>Follower] end end subgraph 生产者 Prod[Producer] end Prod -->|Hash 路由| P0 Prod -->|Hash 路由| P1 Prod -->|Hash 路由| P2 Note over P0,P5: 分区数 = 并行度<br/>分区越多,吞吐量越高

5.2 分区数与吞吐量的关系#

# 分区数对吞吐量的影响
partitions = [1, 3, 6, 12, 24, 48]
base_tps = 50000 # 单分区 5 万 TPS
for p in partitions:
tps = min(p * base_tps, 1000000) # 上限 100 万
print(f"{p} 分区: {tps/10000:.0f} 万 TPS")
# 输出:
# 1 分区: 5 万 TPS
# 3 分区: 15 万 TPS
# 6 分区: 30 万 TPS
# 12 分区: 60 万 TPS
# 24 分区: 100 万 TPS(达上限)
# 48 分区: 100 万 TPS(达上限)

5.3 分区数量的权衡#

因素分区少分区多
吞吐量
延迟可能增加
可用性高(故障影响小)低(故障影响大)
资源消耗高(文件句柄)
顺序保证范围大仅分区内有序

六、页缓存(Page Cache)的利用#

6.1 Kafka 的缓存策略#

Kafka 不在 JVM 堆中缓存消息,而是利用操作系统的页缓存:

flowchart TB subgraph 应用层 Kafka[Kafka Broker<br/>不缓存消息] end subgraph 操作系统 PC[页缓存 Page Cache<br/>自动管理] end subgraph 硬件 RAM[内存] DISK[磁盘] end Kafka -->|写入| PC PC -->|读取| Kafka PC <-->|换入换出| RAM PC -->|异步刷盘| DISK Note over PC: 操作系统自动缓存热点数据<br/>无需应用程序管理

6.2 为什么不用 JVM 堆缓存?#

问题JVM 堆缓存页缓存
GC 压力大对象导致 Full GC无 GC 问题
内存占用对象头开销大紧凑存储
重启恢复数据丢失,需重建操作系统保留
双重缓存页缓存 + 堆缓存仅页缓存
管理复杂度需要自己实现 LRU 等操作系统管理

6.3 页缓存的工作原理#

sequenceDiagram participant P as 生产者 participant K as Kafka participant PC as 页缓存 participant D as 磁盘 participant C as 消费者 P->>K: 发送消息 K->>PC: 写入页缓存(内存速度) K-->>P: 返回成功(不等待刷盘) Note over PC: 异步刷盘,延迟毫秒级 PC->>D: 后台刷盘 C->>K: 拉取消息 K->>PC: 从页缓存读取 PC->>K: 返回数据 K->>C: 返回消息 Note over PC: 热点数据在内存<br/>消费走内存,不走磁盘

6.4 页缓存命中率#

# 页缓存命中率计算
total_reads = 1000000
cache_hits = 950000
cache_misses = total_reads - cache_hits
hit_rate = cache_hits / total_reads * 100
print(f"缓存命中率:{hit_rate:.1f}%")
# 95% 缓存命中意味着什么?
disk_latency = 0.1 # 0.1ms,SSD
memory_latency = 0.0001 # 0.1μs,内存
avg_latency = hit_rate/100 * memory_latency + (1-hit_rate/100) * disk_latency
print(f"平均读取延迟:{avg_latency*1000:.3f} ms")
# 输出:
# 缓存命中率:95.0%
# 平均读取延迟:0.005 ms

七、消费者组的并行消费#

7.1 消费者组模型#

Kafka 的消费者组实现消息的负载均衡和容错:

flowchart TB subgraph Topic: orders P0[Partition 0] P1[Partition 1] P2[Partition 2] P3[Partition 3] end subgraph Consumer Group A C1[Consumer 1<br/>P0, P1] C2[Consumer 2<br/>P2, P3] end subgraph Consumer Group B C3[Consumer 3<br/>P0-P3 全部] end P0 --> C1 P1 --> C1 P2 --> C2 P3 --> C2 P0 --> C3 P1 --> C3 P2 --> C3 P3 --> C3 Note over C1,C2: 同组消费者分担分区 Note over C3: 不同组独立消费

7.2 分区分配策略#

策略说明特点
Range按范围连续分配可能不均匀
RoundRobin轮询分配均匀分布
Sticky尽量保持之前的分配减少 Rebalance 开销
Cooperative增量 Rebalance最小化中断时间

7.3 Rebalance 的代价#

sequenceDiagram participant C1 as Consumer 1 participant C2 as Consumer 2 participant G as Group Coordinator Note over C1,C2: 正常消费 C2->>G: 心跳超时 G->>G: 检测到消费者离线 G->>C1: 触发 Rebalance C1->>C1: 撤销分区分配 G->>G: 重新分配分区 G->>C1: 新的分区分配 Note over C1: Rebalance 期间消费暂停

Rebalance 的影响

  • 消费暂停,分区不可用
  • 重新分配分区,可能重复消费
  • 频繁 Rebalance 导致性能下降

7.4 消费者性能优化#

// 消费者配置优化
Properties props = new Properties();
props.put("fetch.min.bytes", 1024 * 1024); // 最小拉取 1MB
props.put("fetch.max.wait.ms", 500); // 最多等待 500ms
props.put("max.partition.fetch.bytes", 1024 * 1024); // 单分区最大 1MB
props.put("max.poll.records", 500); // 单次最多 500 条

八、与 RabbitMQ、RocketMQ 的性能对比#

8.1 架构差异#

flowchart TB subgraph Kafka K_P[Producer] --> K_Topic[Topic<br/>多分区] K_Topic --> K_B1[Broker 1<br/>Partition 0] K_Topic --> K_B2[Broker 2<br/>Partition 1] K_B1 --> K_C[Consumer] K_B2 --> K_C end subgraph RocketMQ R_P[Producer] --> R_Topic[Topic<br/>多队列] R_Topic --> R_B1[Broker Master] R_Topic --> R_B2[Broker Slave] R_B1 --> R_C[Consumer] end subgraph RabbitMQ Rb_P[Producer] --> Rb_Ex[Exchange] Rb_Ex --> Rb_Q1[Queue 1] Rb_Ex --> Rb_Q2[Queue 2] Rb_Q1 --> Rb_C[Consumer] end

8.2 存储模型对比#

特性KafkaRocketMQRabbitMQ
存储模型追加日志CommitLog + 索引消息队列
删除策略过期删除过期删除消费后删除
消息回溯支持支持不支持
消息过滤客户端过滤服务端 Tag 过滤支持 Exchange
事务消息支持(幂等)原生支持支持
延迟消息不原生支持原生支持原生支持

8.3 性能对比分析#

xychart-beta title "消息队列性能对比" x-axis ["吞吐量(TPS)", "延迟(ms)", "可靠性", "功能丰富度"] y-axis "评分" 0 --> 10 bar [10, 7, 9, 6] bar [7, 8, 9, 8] bar [3, 9, 8, 10]
维度KafkaRocketMQRabbitMQ
吞吐量最高(100万+)高(50万)中等(5万)
延迟低(2-5ms)低(1-5ms)很低(<1ms)
消息顺序分区有序队列有序队列有序
消息可靠性高(副本机制)高(同步刷盘)高(持久化)
功能丰富度中等最高
运维复杂度中等
适用场景大数据、日志金融、电商业务解耦

8.4 选择建议#

场景推荐原因
日志收集、大数据流处理Kafka高吞吐、顺序写、生态完善
金融交易、订单系统RocketMQ事务消息、延迟消息、高可靠
业务解耦、异步任务RabbitMQ功能丰富、部署简单、延迟低
IoT、流计算Kafka高吞吐、与大数据生态集成

九、Kafka 性能设计的核心要点#

9.1 设计哲学#

Kafka 的性能设计遵循一个核心原则:充分利用操作系统和硬件特性,避免重复造轮子

mindmap root((Kafka 高性能)) 顺序写 Append-Only Log 避免随机写 磁盘友好 零拷贝 sendfile mmap 减少 CPU 开销 页缓存 操作系统管理 避免堆缓存 热数据内存读取 批量处理 消息打包 压缩传输 减少网络往返 分区并行 水平扩展 负载均衡 提高并行度

9.2 性能优化技术总结#

技术解决的问题性能提升
顺序写随机写性能差100x
零拷贝数据拷贝开销2-3x
页缓存磁盘访问慢100x(命中时)
批量发送网络往返开销10-100x
压缩网络带宽瓶颈2-3x
分区并行单机性能上限线性扩展
消费者组消费并行度线性扩展

9.3 性能调优建议#

# 生产者关键配置
producer:
batch.size: 16384 # 批次大小 16KB
linger.ms: 5 # 等待凑批次
compression.type: lz4 # LZ4 压缩
buffer.memory: 33554432 # 缓冲区 32MB
acks: 1 # 平衡可靠性和性能
# Broker 关键配置
broker:
num.network.threads: 3 # 网络线程数
num.io.threads: 8 # IO 线程数
log.flush.interval.messages: 10000 # 刷盘间隔
num.partitions: 12 # 默认分区数
# 消费者关键配置
consumer:
fetch.min.bytes: 1048576 # 最小拉取 1MB
max.poll.records: 500 # 单次拉取上限
max.partition.fetch.bytes: 1048576

十、总结#

Kafka 的高性能并非来自某项黑科技,而是对操作系统和硬件特性的深度理解和极致利用:

核心设计原理价值
顺序写追加日志,避免随机写磁盘 IO 性能最大化
零拷贝sendfile 直接传输减少 CPU 和内存开销
页缓存利用 OS 缓存,不重复管理内存利用最优化
批量处理消息打包、压缩网络效率最大化
分区并行分散到多个节点水平扩展能力

Kafka 的设计哲学值得借鉴:与其在应用层优化,不如充分利用底层能力。操作系统已经做了很多优化,理解并利用这些能力,往往比自己在应用层造轮子更有效。

参考资料#

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

为什么 Kafka 这么快
https://blog.souloss.com/posts/why-the-design/why-kafka-is-so-fast/
作者
Souloss
发布于
2024-01-23
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时