retention.ms 到期,消息就消失了吗?这次把一个 topic 的 retention.ms 设成 20 秒,写入 1200 条消息(跨三个 Segment),然后每秒观察一次磁盘目录,记录文件到底在什么时候被改名、什么时候真正从磁盘消失,以及消息还能不能被读到。
命令 · shell $ kafka-topics.sh --create --topic retention-demo --partitions 1 --replication-factor 1 --config retention.ms=20000 # 写入 1200 条 x 约 2000 字节,落在 3 个 Segment:0、512、1024 # 之后每秒 ls 一次 /private/tmp/kafka-lab/node3/retention-demo-0,只在变化时打印
写入结束时(09:50:26):
1031522 00000000000000000000.log
1031656 00000000000000000512.log
354830 00000000000000001024.log
t+0s : 00000000000000000000.log 00000000000000000512.log 00000000000000001024.log
t+12s : 00000000000000000000.log.deleted 00000000000000000512.log.deleted
00000000000000001024.log.deleted 00000000000000001200.log
t+73s : 00000000000000001200.log
最终 : 0 00000000000000001200.log过期之后磁盘上发生了两个阶段:t+12s 时三个旧 Segment 同时被改名成 .log.deleted,同时出现一个新的空 Segment 00000000000000001200.log;t+73s 时 .deleted 文件才真正消失,中间隔了约 61 秒。查到的配置是 file.delete.delay.ms=60000,两者吻合(轮询间隔 1 秒,所以有 1 秒误差)。所以"逻辑删除"和"物理删除"是分开的:改名之后消息已经不可读,但磁盘空间要再过一个延迟期才释放。这个 topic 的 retention.ms 是 20 秒,但轮询是在写入结束之后才开始的,所以 12 秒这个数字不能直接理解成"过期后 12 秒才处理",只能确认这个 broker 的检查间隔(log.retention.check.interval.ms=1000)很短,过期后很快就被处理。
上面输出里,被改名的三个文件包括 00000000000000001024.log——它在写入结束时只有 354830 字节,远没写满 1MB,是当时正在接收写入的活跃 Segment。它也被改名成 .deleted,同时新出现了 00000000000000001200.log(1200 正是下一条消息将要使用的 offset)。也就是说,当整个活跃 Segment 里的消息都已经过期,broker 会先把它滚动成一个新的空 Segment,再把旧的整体删除,而不是留着这个"半满"的文件。上一篇 Log Segment 里用 retention.bytes 实验时活跃 Segment 是被保留的,两个实验的差别在于触发条件:按大小清理的那次,活跃 Segment 被保留了;按时间清理的这次,活跃 Segment 里的消息全部过期,它也被清掉了。为什么会有这个差别,这次没有去读源码验证。
命令 · shell $ kafka-get-offsets.sh --topic retention-demo --time -2 # 最早 $ kafka-get-offsets.sh --topic retention-demo --time -1 # 最新 $ kafka-console-consumer.sh --topic retention-demo --partition 0 --offset 0 --max-messages 1 --timeout-ms 6000
retention-demo:0:1200 retention-demo:0:1200 TimeoutException Processed a total of 0 messages
最早 offset 和最新 offset 都是 1200:offset 0 到 1199 已经不存在,但 offset 没有回退——新消息会从 1200 继续编号,这也是 offset 在 Partition 生命周期里单调递增的证明。从 offset 0 开始消费,结果是读到 0 条消息,命令因为 --timeout-ms 到期以 TimeoutException 结束,没有出现"offset 越界"之类的报错。这里没有继续验证客户端在这种情况下具体走了哪条 offset 重置策略,只能确认:读不到数据,也没有报越界错误。
Retention 的过程是"过期 → 改名 .deleted(此时消息已不可读)→ 等待 file.delete.delay.ms(默认 60 秒)→ 文件真正删除"。清理以 Segment 为单位,时间策略下活跃 Segment 里的消息全部过期时也会被清理,broker 先滚动出新的空 Segment。offset 不会因为数据被删而回退。
每个时间点的文件状态,都是本文上面几节每秒轮询磁盘目录得到的原始记录。