一个 Partition 在磁盘上不是一个大文件,而是一个目录,里面是按大小切开的多个 Segment。这次写入约 5MB 数据到 segment.bytes=1MB 的 topic,直接看磁盘上的文件、用 kafka-dump-log.sh 核对文件名的含义,再设置 retention.bytes 观察删除到底以什么为单位。
命令 · shell $ kafka-topics.sh --create --topic log-segment-demo --partitions 1 --replication-factor 1 # 写入 2500 条 x 约 2000 字节 $ ls -l /private/tmp/kafka-lab/node2/log-segment-demo-0/ $ kafka-dump-log.sh --files .../00000000000000000512.log | grep baseOffset | head -2
00000000000000000000.log 1031522 00000000000000000512.log 1031656 00000000000000001024.log 1032144 00000000000000001536.log 1032144 00000000000000002048.log 911243 <- 还没写满的活跃 Segment --- 00000000000000000512.log 里的第一批 --- baseOffset: 512 lastOffset: 543 count: 32 --- 00000000000000002048.log 里的第一批 --- baseOffset: 2048 lastOffset: 2079 count: 32 --- 00000000000000000000.log 里的最后一批 --- baseOffset: 480 lastOffset: 511 count: 32
2500 条消息产生了 5 个 .log 文件,前 4 个都在 1MB 左右(略超 1MB,因为滚动是以整个 batch 为单位判断的),最后一个 911243 字节还没写满,是当前接收写入的活跃 Segment。文件名不是序号,而是这个 Segment 里第一条消息的 offset:00000000000000000512.log 里第一批的 baseOffset 恰好就是 512,上一个文件最后一批的 lastOffset 是 511,首尾严格衔接。所以给定任意 offset,只需要比较文件名就能知道它在哪个文件里。
上面 ls 输出里同一个基准 offset 对应三个文件:
00000000000000000512.index 120 <- 已滚动的 Segment,索引被裁剪到实际大小 00000000000000000512.timeindex 72 00000000000000002048.index 10485760 <- 活跃 Segment,索引预分配 10MB 00000000000000002048.timeindex 10485756
已经写满的 Segment,索引文件只剩 72–144 字节;仍在写入的活跃 Segment,.index 是 10485760 字节(正好 10MB),.timeindex 是 10485756 字节。这是可见的差别:活跃 Segment 的索引文件先按上限预分配,滚动时才被裁剪成实际内容大小。这也解释了为什么 du 或 ls 看到一个刚建好的空 Partition 也会有 20MB 的文件。索引本身怎么用来定位消息,是下一篇 Offset Index 的内容。
命令 · shell $ kafka-configs.sh --alter --entity-type topics --entity-name log-segment-demo --add-config retention.bytes=2097152 # 等 20 秒(这个实验室的 log.retention.check.interval.ms=1000) $ ls -l .../log-segment-demo-0/ | grep -E '\.log|\.deleted' $ kafka-get-offsets.sh --topic log-segment-demo --time -2 # 最早 offset $ kafka-get-offsets.sh --topic log-segment-demo --time -1 # 最新 offset
1031522 00000000000000000000.log.deleted 1031656 00000000000000000512.log.deleted 1032144 00000000000000001024.log 1032144 00000000000000001536.log 911243 00000000000000002048.log log-segment-demo:0:1024 <- 最早 offset,从 0 跳到 1024 log-segment-demo:0:2500 <- 最新 offset
把 retention.bytes 设成 2MB 后,最老的两个 Segment(起始 offset 0 和 512)整体被改名成 .deleted,配套的 .index、.timeindex、.snapshot 一起被标记,剩下三个文件合计约 3MB。最早可读 offset 从 0 直接变成 1024,恰好是第三个 Segment 的文件名——没有出现"offset 0 到 300 被删、300 之后保留"这种中间状态。删除的最小单位就是 Segment,这也是为什么 retention.bytes 不是一个精确的上限:这次设置 2MB,实际还保留了约 3MB,因为要保证总大小不超过阈值就必须继续删整段,而当前活跃 Segment 是不能删的。
Segment 分段带来三件可验证的事:文件名(起始 offset)让定位可以只靠比较文件名完成;索引文件随 Segment 走、写满后被裁剪;过期数据的清理是直接删整个文件,不需要改写任何内容。这次结果里最容易被忽略的一点是:retention.bytes=2MB 之后磁盘上仍有约 3MB,因为清理粒度是 Segment,不是字节。
每一步的文件名、大小和offset,都是本文上面几节ls磁盘目录、用kafka-dump-log.sh核对得到的原始结果。