上一篇的 Offset Index 解决"给定 offset 找位置",而"给定一个时间点,找到从那时起的第一条消息"靠的是另一份索引:.timeindex。这次写入 300 条时间戳每条相隔 1 秒的消息,读出的时间索引,再用的时间戳查询看结果,最后试探 broker 对时间戳的边界检查。
命令 · shell $ kafka-topics.sh --create --topic time-index-demo --partitions 1 --replication-factor 1 --config index.interval.bytes=200 # Java Producer 发送 300 条,第 i 条的时间戳 = base + i*1000 毫秒,base=1790214409427 $ kafka-dump-log.sh --files /private/tmp/kafka-lab/node1/time-index-demo-0/00000000000000000000.timeindex
timestamp: 1790214412427 offset: 3 timestamp: 1790214415427 offset: 6 timestamp: 1790214418427 offset: 9 timestamp: 1790214421427 offset: 12 ... timestamp: 1790214700427 offset: 291 timestamp: 1790214703427 offset: 294 timestamp: 1790214706427 offset: 297
.log 文件总共 22580 字节、300 条消息,平均每条约 75 字节。这里把 index.interval.bytes 调小到 200(默认是 4096),所以大约每 3 条消息记一条时间索引,条目里的时间戳正好是 base + offset*1000。如果保持默认的 4096 字节,22580 字节大约只会产生 5 个条目(按字节数估算,未实测)。这也是上一篇结论的另一面:索引密度由字节数决定,消息越小,两个条目之间隔的 offset 越多。
命令 · shell $ kafka-get-offsets.sh --topic time-index-demo --time <base + 0> $ kafka-get-offsets.sh --topic time-index-demo --time <base + 150000> $ kafka-get-offsets.sh --topic time-index-demo --time <base + 150500> $ kafka-get-offsets.sh --topic time-index-demo --time <比所有消息都晚的时间戳> $ kafka-console-consumer.sh --topic time-index-demo --partition 0 --offset 151 --max-messages 2 --property print.timestamp=true
base+0ms -> time-index-demo:0:0 base+150000ms -> time-index-demo:0:150 base+150500ms -> time-index-demo:0:151 (比所有消息都晚的时间戳)-> 无输出,退出码 0 CreateTime:1790214560427 Offset:151 v151 CreateTime:1790214561427 Offset:152 v152
查 base+150000 得到 offset 150,那条消息的时间戳恰好等于查询值;查 base+150500(落在第 150 条和第 151 条之间)得到 151,即第一条时间戳不小于查询值的消息。用 --offset 151 消费验证,第一条消息的 CreateTime 是 1790214560427,等于 base + 151000,与 --time 的结果一致。查询一个比所有消息都晚的时间戳,命令没有报错(退出码 0),但没有输出任何 offset。写脚本时如果直接拿这个命令的输出当 offset 用,需要先判断空输出。
命令 · shell $ kafka-configs.sh --entity-type topics --entity-name time-index-demo --describe --all | grep message.timestamp # Java Producer 分别发送时间戳为"当前 +30 分钟"和"当前 +120 分钟"的消息
message.timestamp.type=CreateTime
message.timestamp.after.max.ms=3600000
message.timestamp.before.max.ms=9223372036854775807
未来 30 分钟的时间戳:写入成功
未来 120 分钟的时间戳:InvalidTimestampException: Timestamp 1790221727540 of message with offset 0 is out of range.
The timestamp should be within [-9223370246640247943, 1790218127864]message.timestamp.type=CreateTime 表示消息里的时间戳是 Producer 填的,broker 不改。Producer 可以随意指定时间戳(上面第一个实验就是这么做的),broker 只做一道范围检查:默认允许比当前时间晚最多 1 小时(after.max.ms=3600000),过去方向没有限制(before.max.ms 是 Long 最大值)。结果印证了这一点:+30 分钟成功,+120 分钟被拒绝,错误信息里给出的上界 1790218127864 正是当前时间加 1 小时。所以时间索引查的是"消息里的时间"而不是"消息到达 broker 的时间",业务时间乱序或者 Producer 时钟不准,会直接影响按时间查询的结果。
按时间查消息的流程是:用 .timeindex 把时间戳映射到 offset,再用 Offset Index 找到文件位置。查询语义是"第一条时间戳不小于目标的消息";查询超出范围时没有输出;时间戳默认由 Producer 决定,broker 只限制不能比当前晚超过 1 小时。
每一步都是本文上面两节执行kafka-get-offsets.sh和发送消息得到的原始结果。