第四十二章:Kafka LogSegment 源码深度解析——Segment滚动、Index建立与磁盘文件管理

1 阅读6分钟

第四十二章:Kafka LogSegment 源码深度解析——Segment滚动、Index建立与磁盘文件管理

前言

上一章,我们已经深入分析了 Kafka 的日志系统:

Producer
        │
        ▼
KafkaApis
        │
        ▼
ReplicaManager
        │
        ▼
Partition
        │
        ▼
UnifiedLog

我们知道:

一个 Partition:

对应一个 UnifiedLog。

但是:

UnifiedLog 并不真正保存消息。

真正负责管理磁盘文件的是:

kafka.log.LogSegment

一个 Partition:

实际上是:

UnifiedLog


 ┌────┴────┐
 │    │    │
 ▼    ▼    ▼

Segment Segment Segment

Kafka 能做到:

  • TB 级日志存储
  • 秒级 Broker 恢复
  • 快速 Offset 查询
  • 快速删除历史数据

背后全部依赖:

LogSegment + Index 文件体系。


本章深入:

  • LogSegment源码结构
  • Segment文件组成
  • FileRecords设计
  • OffsetIndex原理
  • TimeIndex原理
  • Segment创建
  • Segment滚动(Roll)
  • Segment恢复
  • Segment删除
  • Kafka磁盘文件管理机制

一、什么是 LogSegment?

源码:

kafka.log.LogSegment

一句话:

LogSegment 是 Kafka 一个日志段(Segment)的完整抽象。

一个 Segment:

对应磁盘上的一组文件。

例如:

00000000000000000000.log
00000000000000000000.index
00000000000000000000.timeindex
00000000000000000000.txnindex

所有文件:

拥有相同的 Base Offset。


二、为什么需要 Segment?

假设:

一个 Topic:

每天:

100GB。

一年:

36TB。

如果:

只有一个:

order.log

问题:

  • 文件太大
  • 删除困难
  • 恢复缓慢
  • Index巨大

所以:

Kafka:

切分日志。

例如:

Segment0

0~999999

↓

Segment1

1000000~

↓

Segment2

2000000~

每个 Segment:

独立管理。


三、Segment文件组成

一个 Segment:

包括:

00000000000000000000.log

↓

消息内容
00000000000000000000.index

↓

Offset Index
00000000000000000000.timeindexTime Index
00000000000000000000.txnindex

↓

事务索引(可选)

还有:

leader-epoch-checkpoint

记录:

Leader Epoch。


四、LogSegment源码结构

源码简化:

class LogSegment {

val log: FileRecords

val offsetIndex: OffsetIndex

val timeIndex: TimeIndex

val txnIndex: TransactionIndex

}

四个核心成员:

成员作用
FileRecords消息存储
OffsetIndexOffset索引
TimeIndex时间索引
TransactionIndex事务索引

五、FileRecords是什么?

源码:

org.apache.kafka.common.record.FileRecords

一句话:

FileRecords 管理 .log 文件。

底层:

使用:

FileChannel

实现:

顺序追加。

流程:

MemoryRecords

↓

FileRecords.append()

↓

FileChannel.write()

↓

Page Cache

六、消息如何追加到 Segment?

调用链:

UnifiedLog

↓

LogSegment.append()

↓

FileRecords.append()

↓

FileChannel.write()

整个过程:

完全:

顺序写。

没有:

随机修改。


七、OffsetIndex是什么?

源码:

kafka.log.OffsetIndex

作用:

快速定位:

Offset。

否则:

如果:

100GB日志。

读取:

Offset=9000000。

需要:

扫描整个文件。

效率极低。


八、OffsetIndex存储结构

Kafka:

不会:

每条消息:

建立索引。

而是:

稀疏索引(Sparse Index)。

例如:

Offset

0

1000

2000

3000

对应:

Position

0

8192

16384

24576

读取:

Offset=2500。

先找到:

2000

然后:

顺序扫描。

因此:

Index很小。


九、为什么采用稀疏索引?

假设:

100亿条消息。

如果:

每条:

建立Index。

Index:

可能:

几十GB。

采用:

稀疏索引:

Index:

通常:

只有几十MB。

节省:

大量内存。


十、TimeIndex是什么?

源码:

kafka.log.TimeIndex

作用:

按时间:

查找消息。

例如:

Producer:

发送:

2026-08-01 12:00

Kafka:

保存:

TimestampOffset

Consumer:

可以:

按时间:

定位Offset。

例如:

offsetsForTimes()

十一、TransactionIndex

事务Topic:

维护:

BeginTxn

CommitTxn

AbortTxn

EOS:

读取:

依赖:

TransactionIndex。

普通Topic:

没有:

TxnIndex。


十二、LogSegment.append()

核心方法:

append()

流程:

收到MemoryRecords

↓

写.log

↓

更新OffsetIndex

↓

更新时间索引

↓

更新大小

十三、什么时候更新Index?

Kafka:

不是:

每条消息:

都更新Index。

由:

log.index.interval.bytes

控制。

默认:

4096 Bytes。

例如:

写:

4KB。

建立:

一条Index。

因此:

Index:

非常小。


十四、Segment什么时候Roll?

Kafka:

当前:

Active Segment。

满足:

任意条件:

Roll。

包括:

文件大小

log.segment.bytes

默认:

1GB。


时间

log.roll.ms

例如:

24小时。


时间戳变化

日志时间:

超过限制。


十五、Segment Roll源码流程

调用:

UnifiedLog.roll()

流程:

关闭旧Segment

↓

Flush

↓

创建新Segment

↓

更新activeSegment

例如:

旧:

00000000000000000000.log

新:

00000000001000000000.log

Base Offset:

就是:

下一条消息Offset。


十六、为什么Base Offset作为文件名?

例如:

00000000003000000000.log

说明:

第一条消息:

Offset:

3000000000

因此:

Kafka:

可以快速:

定位:

Segment。

例如:

请求:

Offset=3200000000

直接:

找到:

对应Segment。

无需:

遍历所有文件。


十七、Segment读取流程

Consumer:

请求:

Offset=2500

流程:

UnifiedLog

↓

找到Segment

↓

OffsetIndex.lookup()

↓

FileRecords.search()

↓

读取消息

时间复杂度:

约:

O(logN)+顺序扫描。


十八、Segment恢复

Broker:

异常退出。

启动:

执行:

LogSegment.recover()

流程:

扫描.log

↓

校验CRC

↓

重建Index

↓

更新LEO

如果:

发现:

坏消息。

Kafka:

截断。


十九、Segment删除

Retention:

到期。

例如:

log.retention.hours=168

Kafka:

删除:

整个:

Segment。

例如:

SegmentA

×

删除

原因:

Segment:

独立。

无需:

移动数据。

效率:

极高。


二十、为什么Kafka删除很快?

传统数据库:

删除:

DELETE

需要:

修改数据。

Kafka:

删除:

整个:

文件。

例如:

rm 00000000000000000000.log

因此:

速度:

极快。


二十一、Segment恢复为什么快?

假设:

Broker:

宕机。

恢复:

不用:

扫描:

全部Partition。

只恢复:

最后几个:

Active Segment。

历史:

Closed Segment:

无需:

重新计算。

因此:

启动:

非常快。


二十二、Segment生命周期

完整流程:

创建

↓

Active

↓

不断Append

↓

Roll

↓

Closed

↓

Retention删除

二十三、Segment状态

主要:

两种:

Active Segment

当前:

写入。


Closed Segment

历史:

只读。


Consumer:

可以:

读取:

所有Segment。

Producer:

只能:

写:

Active。


二十四、源码核心方法

append()

追加消息。


read()

读取消息。


recover()

恢复。


flush()

刷盘。


close()

关闭。


deleteIfExists()

删除。


二十五、LogSegment调用关系

Producer

↓

Partition

↓

UnifiedLog

↓

LogSegment

↓

FileRecords

↓

FileChannel

读取:

Consumer

↓

UnifiedLog

↓

LogSegment

↓

OffsetIndex

↓

FileRecords

二十六、源码核心类总结

作用
LogSegment一个日志段
FileRecords.log文件
OffsetIndexOffset索引
TimeIndex时间索引
TransactionIndex事务索引
LazyIndexIndex懒加载

二十七、面试题

1、为什么Kafka使用Segment?

避免超大文件。

支持快速删除。

支持快速恢复。


2、为什么文件名是Base Offset?

方便快速定位Segment。


3、OffsetIndex为什么是稀疏索引?

减少Index大小。

降低内存占用。


4、什么时候Roll?

文件大小。

时间。

特殊条件。


5、Kafka删除为什么快?

直接删除整个Segment文件。


6、Broker恢复为什么快?

主要恢复活跃Segment。

历史Segment无需重新构建。


二十八、本章总结

LogSegment:

是真正管理磁盘文件的核心对象。

整体结构:

LogSegment

├── FileRecords

├── OffsetIndex

├── TimeIndex

└── TransactionIndex

写入流程:

MemoryRecords

↓

LogSegment.append()

↓

FileRecords.write()

↓

Index更新

↓

Page Cache

读取流程:

Offset

↓

OffsetIndex

↓

Position

↓

FileRecords

↓

Message

牢记:

Kafka 并不是一个巨大日志文件,而是由多个 LogSegment 组成;每个 Segment 又由消息文件和多个索引文件组成,借助稀疏索引、顺序写和按段管理,实现了高吞吐、快速恢复和高效删除。


下一章预告

第四十三章:Kafka FileRecords 源码深度解析——消息格式、RecordBatch 与磁盘存储布局

下一章深入:

  • FileRecords源码结构
  • MemoryRecords 与 FileRecords
  • RecordBatch设计
  • Record格式(V2)
  • CRC校验
  • 压缩消息存储
  • FileChannel写入流程
  • 零拷贝读取流程

彻底理解:

Kafka 一条消息在磁盘文件中到底是如何组织和存储的。