Apache Kafka 的日志格式是其高效存储和快速检索消息的核心设计之一。Kafka 的消息以二进制形式按顺序追加到日志文件中,其格式设计兼顾了存储效率、序列化速度和扩展性。
1. 日志文件的物理结构
Kafka 的日志文件由多个 日志段(Segment) 组成,每个段包含以下文件:
- 数据文件(
.log) :存储实际的消息数据。 - 位移索引文件(
.index) :记录消息位移(Offset)到物理位置的映射。 - 时间戳索引文件(
.timeindex) :记录时间戳到消息位移的映射。
每个日志段以基准位移(Base Offset)命名,例如 00000000000000000000.log,表示该段的第一条消息的 Offset。
2. 单条消息的存储格式
Kafka 的消息格式分为多个版本(V0、V1、V2),不同版本在存储细节上有所优化。以下是 V2 版本(推荐使用) 的详细结构:
(1) 消息的二进制格式
+------------------------------------------------------------------------------------------------+
| Record Batch |
| +------------------------+ +---------------------+ +---------------------------------------+ |
| | Batch Header | | Record 1 | | Record 2 | ... | Record N | |
| +------------------------+ +---------------------+ +---------------------------------------+ |
+------------------------------------------------------------------------------------------------+
(2) 消息批次头(Batch Header)
批次头描述了一组消息的元信息,适用于批量写入的场景:
| 字段 | 大小(字节) | 说明 |
|---|---|---|
| Base Offset | 8 | 当前批次第一条消息的基准位移。 |
| Batch Length | 4 | 整个批次的总长度(包括 Header 和所有 Records)。 |
| Partition Leader Epoch | 4 | Leader 的任期标识,用于防止数据不一致。 |
| Magic | 1 | 格式版本标识符(V2 对应值为 2)。 |
| CRC32 | 4 | 校验和,验证批次完整性。 |
| Attributes | 2 | 控制位(如压缩类型、时间戳类型)。 |
| Last Offset Delta | 4 | 批次中最后一条消息的位移相对于 Base Offset 的差值。 |
| First Timestamp | 8 | 批次中第一条消息的时间戳(支持 CreateTime 或 LogAppendTime)。 |
| Max Timestamp | 8 | 批次中最大的时间戳。 |
| Producer ID | 8 | 生产者 ID(用于事务支持)。 |
| Producer Epoch | 2 | 生产者的任期 ID(用于事务支持)。 |
| Base Sequence | 4 | 生产者发送消息的起始序列号(用于事务支持)。 |
| Records Count | 4 | 批次中包含的消息条数。 |
(3) 单条消息(Record)的格式
+--------------------------------------+
| Record Header |
| +----------------+ +---------------+ |
| | Header Key | | Header Value | |
| +----------------+ +---------------+ |
+--------------------------------------+
| Key Length (变长) | Key (可选) |
+--------------------------------------+
| Value Length (变长) | Value (可选) |
+--------------------------------------+
| 字段 | 大小(字节) | 说明 |
|---|---|---|
| Length | 变长(Varint) | 当前 Record 的总长度。 |
| Attributes | 1 | 控制位(如是否启用压缩)。 |
| Timestamp Delta | 变长(Varlong) | 时间戳相对于批次 First Timestamp 的差值。 |
| Offset Delta | 变长(Varint) | 位移相对于批次 Base Offset 的差值。 |
| Headers | 变长 | 自定义键值对(用于扩展元数据,如 Trace ID)。 |
| Key Length | 变长(Varint) | Key 的长度(-1 表示 Key 为空)。 |
| Key | 变长 | 消息的 Key(可选)。 |
| Value Length | 变长(Varint) | Value 的长度(-1 表示 Value 为空)。 |
| Value | 变长 | 消息的 Value(实际负载数据)。 |
3. 消息格式的版本演进
| 版本 | 引入版本 | 核心改进 |
|---|---|---|
| V0 | Kafka 0.10.0 | 基础格式,无时间戳字段,每条消息独立存储。 |
| V1 | Kafka 0.11.0 | 增加时间戳字段,支持日志压缩(Log Compaction)和事务消息。 |
| V2 | Kafka 2.1.0 | 引入批量记录(Record Batch),减少冗余元数据存储;支持变长整数(Varint)编码,节省空间。 |
4. 消息格式的设计优势
-
批量写入优化:
- V2 版本将多条消息打包为 Record Batch,共享批次头信息(如时间戳、位移),减少重复元数据存储。
-
紧凑存储:
- 使用 Varint/Varlong 编码变长整数,减少小数值的存储空间。
-
扩展性:
- 支持 Headers 字段,可添加自定义元数据(如 Trace ID、路由信息)。
-
事务支持:
- V1 和 V2 版本通过
Producer ID、Producer Epoch和Base Sequence实现精确一次语义(Exactly-Once)。
- V1 和 V2 版本通过
5. 日志格式与索引文件的协同
-
位移索引(
.index) : 存储<Offset, 物理位置>的映射,通过二分查找快速定位消息在.log文件中的位置。 -
时间戳索引(
.timeindex) : 存储<Timestamp, Offset>的映射,支持按时间范围检索消息。 -
查找流程:
- 根据目标 Offset 或时间戳找到对应的日志段。
- 通过索引文件定位到
.log文件的近似位置。 - 顺序扫描少量数据即可找到精确消息。
6. 示例:消息存储的物理布局
假设一个批次包含两条消息:
Record Batch Header:
Base Offset: 100
Records Count: 2
First Timestamp: 1630000000000
Record 1:
Key: "order-123"
Value: "{"status": "shipped"}"
Timestamp Delta: 0
Offset Delta: 0
Record 2:
Key: "order-456"
Value: "{"status": "delivered"}"
Timestamp Delta: 1000 (对应 First Timestamp + 1000ms)
Offset Delta: 1 (对应 Base Offset + 1)
在 .log 文件中的二进制存储如下:
[Batch Header][Record1][Record2]
7. 配置参数
| 参数 | 作用 |
|---|---|
log.message.format.version | 指定 Broker 使用的消息格式版本(如 2.1-IV2)。 |
compression.type | 设置消息压缩类型(如 snappy、gzip、lz4)。 |
message.timestamp.type | 定义时间戳类型(CreateTime 或 LogAppendTime)。 |
总结
Kafka 的日志格式通过 批量存储、变长编码 和 索引优化 实现了高吞吐量与低存储开销:
- V2 格式 是当前最优选择,适合生产环境。
- 索引文件 与日志段协同工作,保障快速检索。
- 事务和压缩 等高级功能依赖特定格式版本,需合理配置。