kafka 的日志存储结构设计充分体现了什么叫做 现代化消息系统, 这也是其区别于上个时代的 rabbitmq、activemq 等前辈的最主要优势;
本文尝试梳理一下 kafka 日志结构的基本原理;
kafka 将每个 partition 的日志划分为多个段 (segment), 每个段是一个独立的文件, 日志分段的主要作用包括:
- 高效存储:将大文件拆分为多个小文件,便于管理和维护;
- 快速读写:通过分段存储,Kafka 可以快速定位和读取数据;
- 数据清理:通过删除旧的日志段,释放磁盘空间;
- 日志压缩:对于启用了日志压缩的 Topic,日志分段是压缩操作的基础;
日志分段是 Kafka 存储设计的核心特性, 是 Kafka 实现高效存储、快速读写和数据清理的基石;
kafka 的日志结构
每个日志段由三个文件组成:
- 数据文件 (.log): 存储实际的消息数据;
- 偏移量索引文件 (.index): 用于根据 offset 快速定位消息;
- 时间索引文件 (.timeindex): 用于根据 timestamp 快速定位消息;
日志段三个文件的名称都是当前段的基准偏移量: 一个 20 位数字, 表示该日志段中第一条消息在目标 partition 中的 offset;
快速定位的原理
.index 索引原理
偏移量索引文件用于根据消息的偏移量 (offset) 快速定位消息的物理位置, 它的结构如下:
- 相对偏移量 (Relative Offset): 当前消息的偏移量与基准偏移量的差值;
- 物理位置 (Position): 消息在数据文件中的起始字节位置;
实际偏移量 = 基准偏移量 + 相对偏移量;
假设有一个偏移量索引文件 (00000000000000012000.index) 的内容如下:1
2
3
4
5
6
7+-----------------+-------------------+
| Relative Offset | Physical Position |
+-----------------+-------------------+
| 0 | 0 |
| 100 | 1024 |
| 200 | 2048 |
+-----------------+-------------------+
- 相对偏移量
0
对应物理字节位置0
; - 相对偏移量
100
对应物理字节位置1024
; - 相对偏移量
200
对应物理字节位置2048
;
以上图为例, 假设消费者需要读取偏移量为 12150 的消息, kafka 会执行以下步骤:
- 定位日志段: 根据偏移量 12150, 确定它属于的日志段 00000000000000012000.index;
- 根据日志段的基准偏移量 12000, 确定该消息的相对偏移量 = 12150 - 12000 = 150;
- 查找索引文件: 在偏移量索引文件中, 使用二分查找找到最接近 150 的索引记录 (相对偏移量 100, 对应物理位置 1024);
- 扫描数据文件: 从物理位置 1024 开始扫描数据文件,直到找到偏移量为 150 的消息;
.timeindex 索引原理
时间戳索引文件用于根据时间戳快速定位消息的偏移量, 它的结构如下:
- 时间戳 (Timestamp): 消息的时间戳;
- 相对偏移量 (Relative Offset): 消息的偏移量与基准偏移量的差值;
每个日志段都有一个基准时间戳 (Base Timestamp), 它是该日志段中第一条消息的时间戳;
假设有一个时间戳索引文件的内容如下:1
2
3
4
5
6
7+----------------+-----------------+
| Timestamp | Relative Offset |
+----------------+-----------------+
| 1633072800000 | 0 |
| 1633072900000 | 100 |
| 1633073000000 | 200 |
+----------------+-----------------+
- 时间戳
1633072800000
对应相对偏移量0
; - 时间戳
1633072900000
对应相对偏移量100
; - 时间戳
1633073000000
对应相对偏移量200
;
通过时间戳索引文件找到消息物理位置的步骤:
- Kafka 会遍历所有日志段, 定位到包含目标时间戳的日志段, 具体步骤如下:
- 获取每个日志段的基准时间戳;
- 比较: 如果目标时间戳大于等于当前日志段的基准时间戳, 并且小于下一个日志段的基准时间戳, 则目标时间戳属于当前日志段;
- 根据消息的 timestamp, kafka 可以使用 二分查找 快速定位消息的偏移量, 然后通过偏移量索引文件找到消息的物理字节位置;
具体示例
kafka 的日志维护
日志段的滚动
- 当日志段达到指定大小时,Kafka 会创建一个新的日志段。
- 日志段的滚动也受时间限制,由配置参数
log.roll.hours
控制,默认值为 7 天。
日志段的清理
- 对于普通 Topic,Kafka 会根据配置参数
log.retention.hours
或log.retention.bytes
删除旧的日志段。 - 对于启用了日志压缩的 Topic,Kafka 会通过 Log Cleaner 清理过期的记录。
日志分段的配置参数
以下是一些与日志分段相关的 Kafka 配置参数:
log.segment.bytes
:每个日志段的最大大小,默认值为 1GB;log.roll.hours
:日志段的时间滚动间隔,默认值为 7 天;log.retention.hours
:日志的保留时间,默认值为 7 天;log.retention.bytes
:日志的保留大小,默认值为 -1(无限制);log.cleanup.policy
:日志清理策略,可以是delete
(默认)或compact
;