做Kafka开发与运维这几年,我收到的告警里出现频率最高的不是CPU也不是内存,而是磁盘。每次磁盘一告警,排查的第一步永远都是先搞清楚Kafka的日志存储模型。所谓日志存储模型,简单说就是每个topic分区在磁盘上对应的一串只追加写入的数据文件,以及围绕这些文件建立的索引、清理和副本同步机制。我第一次接触Kafka时,也被"日志"这两个字带偏过,以为讲的是应用日志采集,翻开源码才看清真相。这篇文章不逐行解读源码,我会用工程视角从上到下把日志存储模型的完整链路讲透。适合正在学Kafka原理、准备Kafka面试,或者已经被磁盘监控告警折磨过的读者。读完你会对"消息到底存哪里""为什么能按offset回放""数据是怎么清理的"这三个问题建立起完整的画面。
1. 为什么Kafka偏要把消息存成"只追加的日志"
1.1 "顺序写比随机写快"不是口号,是磁盘物理特性决定的
很多人第一次看到Kafka的存储设计时都会疑惑:消息不都该放内存Queue里吗?往磁盘写不是自废武功?实测直接推翻了这个直觉。传统机械硬盘的顺序写速度能做到每秒一两百MB,而随机写因为磁头要不停地寻道和旋转等待,每秒只能完成几百次IOPS。举个例子,随机写一个4KB的块如果耗时7ms,那一秒钟顶多写一百来次,也就是400KB左右,和顺序写的差距是三个数量级。SSD虽然随机读快,但随机写会带来块擦除和写放大问题,控制不好反而延迟抖动得厉害。
Kafka的日志存储模型就建立在"必须顺序写"这个约束上。生产端的消息到达broker后,只在日志末尾追加,不做任何随机更新。你可以想象成图书馆里只允许管理员在一排书架后面不断上架新书,永远不需要把旧书抽出来重排,所以整理效率和写入效率都拉满。这个看似简单的决定,其实是把磁盘的物理特性利用到了极致。
1.2 页缓存和零拷贝,让"读旧数据"也不亏
只追加写盘是写路径的答案,那读路径呢?Kafka的招数是"尽量别让数据出内核"。写入的数据先落在页缓存里,消费者如果在缓存窗口内来读,直接命中内存,不需要回磁盘;再加上sendfile零拷贝机制,数据从磁盘文件到网卡socket的路径上不会经过用户态缓冲区,省掉了两次拷贝。这意味着Kafka的读写在很多场景下都快得不像"用磁盘的人"。
我第一次压测时也有点难以置信。生产者疯狂灌消息,消费者实时追着读,CPU占用却不高,磁盘IO也没被打满,秘密就在于大部分热数据都停留在页缓存里,真正的磁盘操作被操作系统调度得又平又顺。这套组合拳,是任何纯用户态内存队列都模拟不出来的底层优势。
1.3 "消费完就删"是误解,日志模型里读和删是两件事
Kafka日志模型和传统队列最大的区别在于:消息并不是被消费者取走就消失。消费者只是在各自维护offset,互不影响。传统队列像一根只能从头吐到尾的管道,Kafka则像一箱可以反复翻阅的档案,只要档案没被清理,任何消费者都能从任意offset重新开始读。
所以Kafka官方文档里用的词是"retention"而不是"delete on consume"。正是这种读写分离的语义,支撑了消费者回放、多消费者组并行消费、以及故障后从上次offset续传这些高级玩法。理解这一点,很多"消息不见了"的错觉就不会产生。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 分区日志的物理降落:Log、Segment、Index这三件套
2.1 一个分区在磁盘上到底长什么样
每个topic的每个分区,在磁盘上对应一个独立目录,命名格式是"topic名-分区号"。我截一个本地实际运行的分区目录过来:
bash复制/tmp/kafka-logs/order_event-0/
├── 00000000000000000000.log
├── 00000000000000000000.index
├── 00000000000000000000.timeindex
├── 00000000000000001024.log
├── 00000000000000001024.index
├── 00000000000000001024.timeindex
├── ...
└── leader-epoch-checkpoint
文件名的20位十进制数字,代表该segment的起始偏移量
