第四十一章:Kafka Log 存储源码深度解析——UnifiedLog、LogSegment与消息追加全过程

0 阅读5分钟

第四十一章: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

查找。

内部:

维护:

TimestampOffset

对应关系。


十六、事务索引(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文件
OffsetIndexOffset定位
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内部到底是如何组织磁盘数据和索引的。