一个总问题
同样是消息队列,为什么 Kafka 单机能跑到每秒百万条,而很多 MQ 只有几万条?答案不在某一个「黑科技」,而在三板斧的叠加:顺序写、零拷贝、批量压缩。这一篇把每一板斧的原理拆开看。
第一板斧:把随机写变成顺序写
先破除一个迷信:磁盘并不慢,慢的是随机寻道。机械硬盘随机写每秒只有几百次,但顺序写可以跑出每秒几百 MB——差距在百倍以上。SSD 上顺序写同样远快于随机写(写放大)。
Kafka 的存储设计全押在这上面:每个 partition 就是一个只追加的日志文件(日志段),新消息永远写在文件末尾,绝不修改中间数据。写入退化为最朴素的「append」,磁盘的好牌全打出来了。MySQL 系列讲过的 redo log 也是同样的哲学——把随机写变顺序写,是存储引擎的第一军规。
代价是查询麻烦:追加的文件怎么按 offset 找消息?Kafka 配了稀疏索引:不为每条消息建索引,每隔一段数据记一个 offset 到文件位置的映射(.index 与 .timeindex 文件,用 mmap 映射进内存)。先二分定位到附近的检查点,再顺序扫一小段——用一点点查找开销,换回纯追加的写入性能。
第二板斧:零拷贝,砍掉中间商
消费者来拉消息,数据要从磁盘走到网卡。传统路径是:
磁盘 --DMA--> 页缓存 --CPU--> 应用缓冲区 --CPU--> socket 缓冲区 --DMA--> 网卡
(4 次拷贝,应用进程只是个搬运工,数据在它手里过一下就走了)Kafka 用操作系统提供的 sendfile 系统调用直接把中间两步砍掉:数据从页缓存直达网卡(零拷贝),CPU 一次都不碰数据,上下文切换也从四次减到两次。这个优化的前提恰恰是上一板斧——消息在文件里连续存放,才能整段整段地搬。叠加页缓存(Kafka 不自己管理缓存,热数据全靠操作系统的 page cache,进程重启缓存还在),消费者拉取命中缓存时,延迟低到接近内存操作。
第三板斧:批量与压缩,摊薄每一字节的路费
一百万条 100 字节的消息,逐条发是三次板斧都救不了的浪费:每条都要过一遍协议栈、记一次日志、做一次网络往返。Kafka 的生产端把消息攒批发送:攒够 batch.size(默认 16KB)或等满 linger.ms(默认 0,常调到 5-20ms),打包一起发。一批一万条消息,协议开销、磁盘写、网络往返全部摊薄一万倍。
攒起的批还能整体压缩(lz4、snappy、zstd、gzip)。文本类日志压缩比轻松到 5-10 倍,且 Kafka 支持端到端压缩:生产端压缩,Broker 原样落盘,消费者解压——全程不拆包,磁盘和网络一起省。代价是 linger.ms 带来的几毫秒延迟和 CPU 的压缩开销,吞吐型场景这笔账稳赚。
三板斧的成本账
| 技术 | 收益 | 代价/边界 |
|---|---|---|
| 顺序追加写 | 写入快百倍 | 不支持原地修改,删除靠整段过期 |
| 零拷贝 | CPU 空转减少,延迟降低 | 依赖消息连续存储;SSL 加密场景失效 |
| 攒批压缩 | 吞吐量级提升,带宽省数倍 | 几毫秒攒批延迟;消耗 CPU |
反过来指导业务
理解原理是为了用对姿势。三条落地建议:一是别往 Kafka 里塞大消息(图片、文件走对象存储,消息里只放 URL),大消息一破坏顺序写和零拷贝的前提,三板斧全废;二是吞吐优先的 Topic 才开攒批压缩,毫秒级敏感的业务把 linger.ms 设回 0;三是给 Broker 留足内存和页缓存,缓存命中率高,零拷贝才跑得欢。
小结
Kafka 的高吞吐没有魔法:顺序写把磁盘用对,零拷贝把 CPU 省下,批量压缩把路费摊薄。三者环环相扣,前提都是「消息按序连续存放」这个最朴素的设计决定。
性能说完了,下一篇说 Kafka 最让运维头疼的机制:分区与消费者组——Rebalance 风暴是怎么刮起来的,offset 的三种语义又该怎么选。
评论 (0)