工程圈有一条流传已久的测试题:“磁盘比内存慢得多,那为什么一个主要靠磁盘转的系统,能做到每秒吞吐上百万条消息?”大部分人在第一句就会卡住。而 ByteByteGo 频道(系统设计领域头部频道,创办人 Alex Xu,《System Design Interview》作者)的一期动画短课,把这道题拆得明明白白——这就是本期要深读的《Why is Kafka fast?》(Kafka 为什么快)。
这条视频讲的是 Apache Kafka——一个 2011 年从 LinkedIn 开源出来的消息系统,如今是几乎所有大型互联网公司的数据主动脉:订单、埋点、日志、风控事件,进出都要过一遍它。视频用不到二十分钟的动画,回答了一个反直觉的问题:它凭什么这么快? 答案不是黑科技,而是一组“反常识的放弃”:放弃随机访问、放弃用户态搬运、放弃逐条处理、放弃自建缓存。看完你会发现,所谓性能设计,很多时候不是“多做”,而是“少做”。
本文按视频的叙事顺序逐层拆解,再补上它没讲透的理论源头与边界条件——让你没看过视频也能懂,看过视频能多懂一层。
| 项目 | 内容 |
|---|---|
| 视频标题 | Why is Kafka so Fast?(Kafka 为什么这么快?) |
| 频道 | ByteByteGo(Alex Xu 主理的系统设计频道) |
| 形式 | 动画讲解,约10分钟级别 |
| 播放量 | 系统设计类长青爆款,长期被推荐(据频道公开页面量级,撰稿环境未能实时精确核对) |
| 核心主题 | Kafka 高吞吐背后的5个设计决策及其通用原理 |
说明:本篇为系列第2轮(编程与开发方向)。撰写环境的抓取通道受限,播放量等动态数据以频道公开页面的常见量级标注,未做实时精确核对;文中涉及的技术机制(sendfile、页缓存、Kafka 架构)均以 Linux 手册页、Apache Kafka 官方文档及原始论文为准。
(视频内容)视频开篇先立好舞台:Kafka 本质上是一个分布式的、只追加的日志(append-only log)。生产者把消息写进某个主题(topic)的末尾,消费者按偏移量(offset)顺序读走,互不相见、互不等待。它存在的意义是把系统之间“点对点、同步等”的调用,改造成“写进公共管道、各自消费”的异步解耦——下游宕机不拖垮上游,流量洪峰先攒着再消化。
(延伸分析)很多人把 Kafka 当“消息队列”,但视频里那个动画其实在暗示更准确的心智模型:它是一个提交日志(commit log),队列只是副产品。这个区别决定了后面所有设计的走向——队列的天然语义是“取出即删除、随机消费”,而日志的语义是“只追加、可重放、按位置读”。Kafka 的快,一半来自它选择了后者:日志模型天然排斥随机访问,而随机访问恰恰是存储系统一切噩梦的根源。理解了这一点,后面五个“技巧”就不再是五个孤立的知识点,而是同一个决定在不同层面的展开。
(视频内容)直觉告诉我们磁盘慢,但这个直觉默认的是随机访问:机械硬盘每次读写要等磁头寻道、盘片旋转,毫秒级的延迟在每秒上万的随机请求面前就是灾难。而 Kafka 的日志只追加、只顺序读写——磁头一旦定位就沿着磁道连续扫过去,吞吐瞬间上台阶。视频用了一个流传很广的对比:机械盘的顺序读写性能,可以逼近甚至超过它自己随机读写的成百上千倍,比很多人想象的“磁盘速度”高一两个数量级。
(延伸分析)这是全片最值得刻进脑子的一条:谈论“磁盘慢不慢”之前,必须先回答“怎么访问”。顺序化把慢介质用出了快的感觉,这个思想远不止 Kafka 一家在用:数据库的 WAL(预写日志)为什么先记日志再改数据?因为追加日志是顺序写。LSM-Tree 存储引擎(RocksDB 一族)为什么把随机更新先攒成内存表再批量刷盘?为了把随机变顺序。甚至 SSD 的固件(FTL)也在偷偷做同样的整理。“日志是数据结构的灵魂”——这句话是 Jay Kreps(Kafka 创造者之一)后来那篇著名长文《The Log》的核心论点,而 Kafka 就是这句话的产品化。
(视频内容)视频接着把镜头对准一个大多数程序员从没检查过的地方:数据从磁盘走到网卡,到底要搬动几次?不用任何优化时,答案是四次拷贝、若干次内核/用户态上下文切换:磁盘→内核页缓存→用户空间缓冲区→套接字缓冲区→网卡。每一次搬运都要 CPU 亲手把字节从一块内存复制到另一块,外加模式切换的开销。而对 Kafka 来说,从磁盘读消息发给消费者是最高频的动作,这条通路上却存在一个尴尬的事实——数据只是路过用户空间,根本没人需要在用户空间处理它。
于是 Kafka 把发送动作交给 Linux 的 sendfile() 系统调用:数据直接从页缓存送进网卡,跳过用户态的两次拷贝和两次模式切换。CPU 不再搬运字节,只负责“指路”;如果网卡支持 gather(散射-聚合)DMA,连网卡侧都只需拿到内存描述符而非数据本体。视频用动画把这条通路画得非常直观:原来的四段旅程,被裁成两段。
(延伸分析)这里要补一个视频没点破的关键细节:零拷贝主要发生在“消费者拉取”这条读路径上——写入路径靠的是另一招(顺序追加落盘),生产消息并不走 sendfile。读多写少的拉取场景恰好是消息系统最吃性能的地方,所以这一刀砍在了刀刃上。sendfile 并不是什么新玩具,它是 2000 年前后就进入 Linux 的老接口,最早为了加速 Web 服务器发静态文件而生——Kafka 的功劳不是发明它,而是在自己的核心通路上把它用尽。
(视频内容)第三招是批量(batching):生产者不逐条发送,而是把一个小时间窗内的消息攒成一批发出去;broker 端把同一批消息连续写进日志;消费者拉取时也是一次拖走一整段。网络传输和磁盘写入都有高昂的“固定成本”(协议握手、系统调用、I/O 提交),逐条处理等于为每条几百字节的消息支付整趟运费;攒批发货则把这笔钱摊到几万条消息头上。配套的一手是压缩:整批一起压缩远比逐条压缩划算,因为相邻消息往往高度相似,压缩率极高。
第四招是分区(partition):一个主题在物理上被切成多个分区,分布在不同机器上,各自独立追加。单机的顺序 I/O 再快也有上限,想要更多吞吐,加分区、加机器,吞吐线性往上走。视频强调:这让 Kafka 的性能曲线不是“优化出更快的单点”,而是“可横向复制的标准单元”。
(延伸分析)批量这招值得单独咀嚼:它本质上是在用延迟换吞吐——攒批必然引入几毫秒到几十毫秒的等待,这正是 Kafka 被设计给“日志流”而非“逐条事务”用的原因之一。工程里很多性能手段都带着这层交换:视频里的每一招都不是免费的,认清代价才算真看懂。而分区并行则是把前两招乘上一个 N:顺序 I/O 与批量是“把单点做对”,分区是“把做对的东西复制到水平方向”——前者是必要条件,后者是规模化的杠杆。
(视频内容)最后一招最反直觉:Kafka 不自己建内存缓存。大多数系统会在进程里维护一块精心管理的缓存区,Kafka 却直接依赖操作系统的页缓存(page cache):读写都从 OS 托管的缓存走,进程自己只管顺序地写文件、顺序地读文件。好处有三:缓存命中时不碰磁盘;JVM 不用为巨大的缓存堆做垃圾回收(避免了 GC 抖动);哪怕 broker 进程重启,页缓存还在操作系统手里,热数据依然热——进程死了缓存不陪葬。
(延伸分析)这一条其实是“克制”的教科书案例:自己造缓存,意味着缓存失效、预取、并发、GC 压力一堆问题自己扛;而页缓存是内核团队优化了几十年的成熟实现,免费、稳健、进程无关。Kafka 的设计文档里明确写过这个取舍——相信文件系统和页缓存,而不是绕开它们。这与它在零拷贝上的思路一脉相承:不重新发明内核已经做好的事,把自己的代码从数据通路上挪走。整个视频看下来,“快”的五个支柱没有一个需要天才发现,需要的只是始终如一地顺着日志模型把“少做”贯彻到底。
视频里的每个点都有扎实的出处,值得对照深挖:
sendfile(2) 的说明直接使用了"zero-copy"一词;它的引入是为了让 Web 服务器发送静态文件时绕开用户态搬运。Kafka 消费路径只是这个老接口的一次成功移植。证据充分的部分:顺序 I/O、页缓存、批量压缩、分区扩展,全部是 Kafka 官方文档与原始论文白纸黑字写明的设计决策,机制层面无争议。sendfile 零拷贝也是消费者路径的确定事实。
需要校准的部分,有四处:
第一,性能审查先查“搬运次数”,再查算法。 多数性能问题不在算法复杂度,而在数据通路上多出来的那两次拷贝、那几次上下文切换、那趟多余的序列化。下次你的服务在压测下掉链子,先画一张“数据从哪来、到哪去、中途被复制几次”的图——Kafka 给的启示是:把没人需要的搬运环节直接从通路上拆掉,比任何微优化都值钱。
第二,把“顺序化”当成通用设计本能。 面对随机访问的慢,先问一句:能不能把访问模式改成顺序?写日志、攒批次、批量刷盘、追加存储——从数据库 WAL 到 SSD 固件,这个思想在每一层存储栈里反复出现。识别出“这个系统其实是日志”的那一刻,优化方案往往自己浮出来。
第三,为每个“快”索取它的放弃清单。 下次有人向你推销一个快得惊人的系统,别先问“它为什么快”,先问“它不做哪些事”。Kafka 的答案清单(不做随机读、不做事务、不做逐条低延迟)正是它快的全部预算。一张“不支持清单”比一页性能数字更能告诉你这个系统适不适合你。
flowchart TD
Q["Kafka为什么快"] --> A["支柱一:顺序I/O"]
Q --> B["支柱二:零拷贝"]
Q --> C["支柱三:批量+压缩"]
Q --> D["支柱四:分区并行"]
Q --> E["支柱五:页缓存"]
A --> A1["顺序与随机<br/>差1~3个数量级"]
B --> B1["sendfile<br/>砍掉用户态搬运"]
C --> C1["摊薄I/O与网络固定成本"]
D --> D1["加机器即加吞吐"]
E --> E1["进程重启缓存不陪葬"]
A1 --> Z["共同前提:只追加<br/>只整块、不随机"]
B1 --> Z
C1 --> Z
D1 --> Z
E1 --> Z
Z --> W["快,是放弃换来的"]
本文由「51学通信」技术团队出品。51学通信由通信行业18年老兵爱卫生(微信ID: gprshome201101)维护,聚焦5G/6G核心网、3GPP标准解读、通信前沿技术深度分析。 出版作品有《5G核心网原理与实践》《IMS原理与实践》等4本通信专业书籍。知识星球「51学通信」提供深度技术答疑和行业洞察。
[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
