第四十一章:Kafka Log 存储源码深度解析——UnifiedLog、LogSegment 与消息追加全过程
前言
上一章,我们已经深入分析了 Partition:
Producer
↓
KafkaApis
↓
ReplicaManager
↓
Partition
最终:
消息真正写入的位置:
就是:
UnifiedLog
很多人学习 Kafka 时都会认为:
Kafka 就是把消息 append 到一个文件。
实际上:
Kafka 的日志系统远比想象复杂。
一个 Partition:
不是一个文件。
而是一组:
- Segment
- Index
- TimeIndex
- TransactionIndex
- Snapshot
共同组成。
Kafka 能做到:
- 百万级写入
- 顺序磁盘 IO
- 秒级恢复
- 快速定位 Offset
背后全部依赖:
UnifiedLog。
本章深入:
- UnifiedLog整体架构
- LogSegment设计
- Log追加全过程
- Offset分配
- Index建立
- Segment滚动
- Flush机制
- Crash Recovery
- Log恢复源码
- Kafka日志目录结构
一、UnifiedLog是什么?
源码:
kafka.log.UnifiedLog
一句话:
UnifiedLog 是 Kafka 一个 Partition 的完整日志管理器。
它负责:
- 消息追加
- 消息读取
- Segment管理
- Offset管理
- Flush
- Recovery
- Retention
例如:
Topic:
order-topic
Partition:
P0
对应:
UnifiedLog
整个Partition:
只有一个UnifiedLog。
二、UnifiedLog在整体架构中的位置
完整链路:
Producer
↓
KafkaApis
↓
ReplicaManager
↓
Partition
↓
UnifiedLog
↓
LogSegment
↓
File
可以看到:
Partition:
管理状态。
UnifiedLog:
管理数据。
三、Kafka日志目录结构
假设:
Topic:
order
Partition:
0
Broker目录:
/data/kafka-logs/
order-0/
进入:
order-0/
00000000000000000000.log
00000000000000000000.index
00000000000000000000.timeindex
leader-epoch-checkpoint
partition.metadata
如果启用事务:
还会有:
00000000000000000000.txnindex
四、为什么不是一个大文件?
假设:
一个Partition:
保存:
20TB。
如果:
只有一个:
log
会出现:
- 文件过大
- 删除困难
- 恢复慢
- 查找慢
所以:
Kafka采用:
Segment。
五、什么是LogSegment?
源码:
kafka.log.LogSegment
一句话:
一个Segment就是一个独立的小日志文件。
例如:
00000000000000000000.log
↓
保存
0~999999
下一个:
00000000001000000000.log
↓
保存
1000000~
一个Partition:
由多个Segment组成。
六、UnifiedLog内部结构
源码:
class UnifiedLog {
segments
activeSegment
logStartOffset
logEndOffset
}
主要成员:
segments
保存:
所有Segment。
例如:
Segment1
Segment2
Segment3
activeSegment
当前写入Segment。
所有新消息:
都写这里。
logStartOffset
最早Offset。
logEndOffset
最新Offset。
七、消息追加入口
Producer:
最终:
调用:
UnifiedLog.appendAsLeader()
Follower:
调用:
appendAsFollower()
Leader:
负责:
分配Offset。
Follower:
直接使用Leader Offset。
八、appendAsLeader源码流程
核心:
appendAsLeader()
整体:
收到消息
↓
校验
↓
分配Offset
↓
写入Active Segment
↓
更新Index
↓
更新LEO
↓
返回Offset
九、第一步:校验消息
Kafka首先:
检查:
- Magic Version
- Timestamp
- CRC
- Message格式
例如:
消息:
MessageBatch
解析:
MemoryRecords
十、第二步:分配Offset
Leader:
维护:
LEO
例如:
当前:
LEO=100
来了:
3条消息。
Kafka:
分配:
100
101
102
然后:
更新:
LEO=103
十一、第三步:选择Segment
Kafka:
找到:
activeSegment
例如:
当前:
Segment:
00000000002000000000.log
所有消息:
追加进去。
十二、第四步:追加日志
调用:
LogSegment.append()
继续:
调用:
FileRecords.append()
最终:
写:
FileChannel.write()
消息:
进入:
.log
文件。
十三、Kafka为什么写得快?
原因:
全部:
顺序写。
例如:
100
101
102
103
一直:
Append。
没有:
随机修改。
磁盘:
顺序IO性能:
极高。
十四、第五步:更新OffsetIndex
日志:
写入后。
建立:
Offset Index。
例如:
Offset
100
200
300
对应:
Position
0
8192
16384
以后:
查找:
Offset=250。
先:
找:
Index。
再:
定位:
Log。
不用:
扫描全部。
十五、TimeIndex更新
Kafka:
支持:
按时间查消息。
例如:
2026-08-01
查找。
内部:
维护:
Timestamp
↓
Offset
对应关系。
十六、事务索引(TxnIndex)
开启事务:
Kafka:
记录:
事务边界。
例如:
BeginTxn
...
CommitTxn
Consumer:
EOS读取:
依赖:
TxnIndex。
十七、什么时候滚动Segment?
Kafka:
不会:
无限写。
满足:
任意条件:
创建:
新Segment。
例如:
log.segment.bytes
默认:
1GB。
达到:
1GB。
滚动。
log.roll.ms
达到:
时间。
滚动。
十八、Segment Roll流程
当前:
SegmentA
写满。
Kafka:
执行:
close()
↓
create()
↓
activeSegment
↓
SegmentB
后续:
写:
SegmentB。
十九、为什么需要多个Segment?
方便:
删除。
例如:
保留:
7天。
Kafka:
直接:
删除:
旧Segment。
不用:
扫描文件。
效率:
极高。
二十、消息读取流程
Consumer:
请求:
Offset=250
流程:
UnifiedLog.read()
↓
OffsetIndex
↓
LogSegment
↓
FileRecords
↓
Message
复杂度:
接近:
O(logN)。
二十一、Flush机制
Kafka:
不是:
每条消息:
fsync。
否则:
性能极低。
默认:
依赖:
OS:
Page Cache
后台:
Flush。
也可以:
配置:
log.flush.interval.messages
log.flush.interval.ms
二十二、Crash Recovery
Broker:
异常退出。
启动:
恢复:
UnifiedLog.recover()
流程:
扫描Segment
↓
检查CRC
↓
截断损坏消息
↓
恢复Index
↓
更新LEO
二十三、Index恢复
如果:
Index丢失。
Kafka:
怎么办?
答案:
重新扫描:
.log
重新建立:
.index
.timeindex
因此:
Index:
属于:
可重建数据。
二十四、日志删除机制
Retention:
到期。
例如:
log.retention.hours=168
Kafka:
删除:
整个:
Segment。
例如:
Segment1
×
删除
不会:
删除:
单条消息。
二十五、Compact模式
如果:
Topic:
开启:
cleanup.policy=compact
Kafka:
保留:
同一个Key:
最新Value。
例如:
user1=A
user1=B
user1=C
最终:
保留:
user1=C
详细机制:
我们将在后续日志清理章节深入分析。
二十六、UnifiedLog源码核心方法
appendAsLeader()
Leader写。
appendAsFollower()
Follower同步。
read()
读取消息。
roll()
创建新Segment。
recover()
恢复日志。
deleteOldSegments()
删除日志。
二十七、完整消息写入链路
Producer
↓
KafkaApis
↓
ReplicaManager
↓
Partition
↓
UnifiedLog
↓
appendAsLeader()
↓
LogSegment
↓
FileRecords
↓
FileChannel.write()
↓
Page Cache
二十八、完整读取链路
Consumer
↓
FetchRequest
↓
UnifiedLog.read()
↓
OffsetIndex
↓
LogSegment
↓
FileRecords
↓
Message
二十九、源码核心类总结
| 类 | 作用 |
|---|---|
| UnifiedLog | 管理整个Partition日志 |
| LogSegment | 管理一个日志段 |
| FileRecords | 管理.log文件 |
| OffsetIndex | Offset定位 |
| TimeIndex | 时间定位 |
| TransactionIndex | 事务定位 |
三十、面试题
1、Kafka一个Partition对应几个Log?
一个UnifiedLog。
2、为什么需要Segment?
避免超大文件。
方便删除。
快速恢复。
3、Offset如何定位?
OffsetIndex。
4、Kafka为什么顺序写?
提高磁盘吞吐。
5、Broker异常如何恢复?
recover扫描日志。
重建Index。
6、为什么Index可以删除?
因为:
可以通过Log重建。
三十一、本章总结
Kafka日志系统:
核心:
UnifiedLog
↓
LogSegment
↓
FileRecords
↓
Disk
写入流程:
Producer
↓
appendAsLeader
↓
Offset
↓
Segment
↓
Index
↓
Page Cache
读取流程:
Consumer
↓
OffsetIndex
↓
Segment
↓
Message
牢记:
Kafka真正保存消息的不是Partition,而是UnifiedLog;真正落盘的不是UnifiedLog,而是一个个LogSegment;真正实现高性能顺序写的是FileRecords和底层FileChannel。
下一章预告
第四十二章:Kafka LogSegment 源码深度解析——Segment滚动、Index建立与磁盘文件管理
下一章深入:
- LogSegment源码结构
- Segment创建流程
- FileRecords设计
- OffsetIndex源码
- TimeIndex源码
- Segment Roll源码
- Segment恢复
- Segment删除机制
彻底理解:
Kafka一个Segment内部到底是如何组织磁盘数据和索引的。