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 可以使用 二分查找 快速定位消息的偏移量, 然后通过偏移量索引文件找到消息的物理字节位置;

具体示例

offsetIndex 的直观逻辑
offsetIndex 的直观逻辑

kafka 的日志维护

日志段的滚动

  • 当日志段达到指定大小时,Kafka 会创建一个新的日志段。
  • 日志段的滚动也受时间限制,由配置参数 log.roll.hours 控制,默认值为 7 天。

日志段的清理

  • 对于普通 Topic,Kafka 会根据配置参数 log.retention.hourslog.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;