写点什么

大数据 -69 Kafka 存储结构解析:日志文件与索引文件的内部机制

作者:武子康
  • 2025-08-16
    山东
  • 本文字数:4265 字

    阅读完需:约 14 分钟

大数据-69 Kafka 存储结构解析:日志文件与索引文件的内部机制

点一下关注吧!!!非常感谢!!持续更新!!!

🚀 AI 篇持续更新中!(长期更新)

AI 炼丹日志-31- 千呼万唤始出来 GPT-5 发布!“快的模型 + 深度思考模型 + 实时路由”,持续打造实用 AI 工具指南!📐🤖

💻 Java 篇正式开启!(300 篇)

目前 2025 年 08 月 11 日更新到:Java-94 深入浅出 MySQL EXPLAIN 详解:索引分析与查询优化详解 MyBatis 已完结,Spring 已完结,Nginx 已完结,Tomcat 已完结,分布式服务正在更新!深入浅出助你打牢基础!

📊 大数据板块已完成多项干货更新(300 篇):

包括 Hadoop、Hive、Kafka、Flink、ClickHouse、Elasticsearch 等二十余项核心组件,覆盖离线+实时数仓全栈!大数据-278 Spark MLib - 基础介绍 机器学习算法 梯度提升树 GBDT 案例 详解


章节内容

上节我们完成了如下内容:


  • 物理存储 日志存储概述

  • LogSegment

  • 日志切分文件

  • 索引切分过程

  • 索引文件等等


索引文件

Kafka 的消息存储采用分段存储机制,每个分区由多个 Segment 文件组成,主要包括以下三类文件:


  1. 日志文件(.log)

  2. 存储实际的消息内容,采用顺序追加写入方式

  3. 文件名基于文件中第一条消息的偏移量命名,如"00000000000000000000.log"

  4. 虽然 Kafka 理论上支持 64 位偏移量,但实践中采用 20 位数字(可表示约 1 百万 TB 数据)已足够

  5. 默认每个 Segment 文件大小上限为 1GB(通过 log.segment.bytes 参数配置)

  6. 偏移量索引文件(.index)

  7. 采用稀疏索引结构,记录消息偏移量到物理位置的映射

  8. 索引条目包含两个字段:相对偏移量(4 字节)和物理位置(4 字节)

  9. 初始分配 10MB 空间,随着 Segment 滚动会修剪为实际使用大小

  10. 例如:偏移量索引可能记录"offset:500 → position:1024"这样的映射关系

  11. 时间戳索引文件(.timeindex)

  12. 记录时间戳与偏移量的对应关系

  13. 主要用于支持按时间戳查询消息的功能

  14. 同样初始分配 10MB 空间,后续会动态调整

  15. 索引格式为:时间戳(8 字节)+ 相对偏移量(4 字节)


文件管理机制:


  • 新 Segment 创建时会同时生成.log、.index、.timeindex 三个文件

  • 文件命名保持一致性,如"00000000000000000000.log"、"00000000000000000000.index"、"00000000000000000000.timeindex"

  • 当.log 文件达到大小阈值时触发 Segment 滚动:

  • 关闭当前文件组

  • 创建新的文件组(基于新的起始偏移量命名)

  • 对索引文件进行空间整理


应用场景示例:


  • 消费者请求 offset=500 的消息时:

  • 先查询.index 文件找到最接近的索引点(如 offset:400 → position:800)

  • 从.log 文件的 800 位置开始顺序扫描,直到找到 offset=500 的消息

  • 管理员需要查询某时间点(如 2023-01-01 00:00:00)之后的消息时:

  • 查询.timeindex 文件找到对应时间戳的偏移量

  • 再通过.index 文件定位物理位置

  • 最后从.log 文件读取实际消息内容


这种设计通过空间换时间的方式,在保证写入性能的同时,提供了高效的消息检索能力。


index 和 timeindex 内容如下:


创建主题

kafka-topics.sh --zookeeper h121.wzk.icu:2181 --create --topic wzk_test_demo_05 --partitions 1 --replication-factor 1 --config segment.bytes=104857600
复制代码


执行结果如下图:


创建消息

for i in `seq 10000000`; do echo "hello kangkang $i" >> test_data.txt; done
复制代码

生产消息

kafka-console-producer.sh --broker-list h121.wzk.icu:9092 --topic wzk_test_demo_05 < test_data.txt
复制代码


运行结果如下图:


查看存储

cd /opt/kafka-logscd wzk_test_demo_05-0ll
复制代码


运行结果如下图:


查看详细

如果想查看这些文件,可以使用 Kafka 提供的 Shell 工具来完成查看和操作。Kafka 提供了多个 Shell 脚本,包括kafka-console-consumer.shkafka-run-class.sh等,可以用来查看和解析消息文件。以下是几个关键信息的详细说明:


  1. Offset(偏移量)

  2. 是一个单调递增的整数,表示消息在分区中的唯一标识。

  3. 每个 offset 对应一条消息的位置,消费者通过跟踪 offset 来维护消费进度。

  4. 例如:offset=100 表示这是该分区中的第 100 条消息。

  5. Position(位置)

  6. 表示消息批(message batch)的字节数,用于计算消息在磁盘上的物理存储地址。

  7. 帮助 Kafka 快速定位消息在日志文件中的具体位置。

  8. CreateTime(创建时间)

  9. 消息被生产者发送到 Kafka 的时间戳,格式为 Unix 时间戳(毫秒级)。

  10. 例如:CreateTime=1651234567890 表示消息创建时间为 2022 年 4 月 29 日。

  11. Magic(消息格式版本)

  12. 标识消息的格式版本:

  13. 0:代表 V0 版本(Kafka 0.10.0 之前)

  14. 1:代表 V1 版本(Kafka 0.10.0-0.11.0)

  15. 2:代表 V2 版本(Kafka 0.11.0 及之后)

  16. V2 版本支持更高效的批量消息存储和压缩。

  17. Compresscodec(压缩编解码器)

  18. 表示消息采用的压缩算法:

  19. None(未压缩)

  20. 0:GZIP(高压缩比,但 CPU 消耗较大)

  21. 1:Snappy(快速压缩,适合低延迟场景)

  22. 2:LZ4(平衡压缩比和速度)

  23. 3:Zstandard(Kafka 2.1.0 引入的高效压缩)

  24. 例如:Compresscodec=2 表示使用 Snappy 压缩算法。

  25. CRC(循环冗余校验)

  26. 对消息所有字段计算得到的 32 位校验值,用于检测消息在传输或存储过程中是否损坏。

  27. 如果 CRC 校验失败,Kafka 会丢弃该消息。


使用示例:


# 查看某个topic的消息内容及其元数据bin/kafka-run-class.sh kafka.tools.DumpLogSegments --files 00000000000000000000.log --print-data-log
复制代码


输出示例会显示每条消息的 offset、position、CreateTime 等元数据信息,帮助开发者调试和分析消息内容。


kafka-run-class.sh kafka.tools.DumpLogSegments --files 00000000000000000000.log --print-data-log | head
复制代码


执行结果如下图:


消息偏移

消息存储

  • 消息内容保存在 log 日志文件中

  • 消息封装为 Record,追加到 log 日志文件末尾,采用的是顺序写模式。

  • 一个 topic 的不同分区,可认为是 queue,顺序写入接受到的消息



消费者有 Offset,下图中,消费者 A 消费的 Offset 是 9,消费者 B 消费的 Offset 是 11,不同的消费者 Offset 是交给一个内部公共 topic 来记录的。



时间戳索引文件,它的作用是可以让用户查询某个时间段的内的消息,它一条数据的结构是时间戳(8 byte) + 相对 Offset(4 byte)。如果要使用这个索引文件,首先需要通过时间范围,找到相对 Offset,然后再去对应的 Index 文件中找到 Position 信息,然后才能遍历 log 文件,它也需要使用上面说的 Index 文件的。


但是 Producer 生产消息可以指定消息的时间戳,这可能将导致消息的时间戳不一定有先后顺序,因为尽量不要生产消息时指定时间戳。

偏移量索引

  • 索引文件存储位置与命名:Kafka 的索引信息保存在与日志文件同名的.index文件中(例如topic-partition-0.index)。这些索引文件与对应的.log数据文件存放在同一目录下,共同组成一个完整的分区存储单元。

  • 稀疏索引机制

  • 日志写入时默认每积累 4KB 数据(由log.index.interval.bytes参数配置,可调整)才会生成一条索引记录

  • 这种设计形成了稀疏索引结构,例如在 1GB 的日志文件中可能只包含约 25 万条索引(1GB/4KB),相比为每条消息建索引可减少 99%以上的索引量

  • 实际查询时需要通过二分查找定位最近的索引点,再顺序扫描找到目标消息

  • 日志文件物理结构

  • 采用顺序追加写入方式,每条记录包含:

  • Message:实际的消息内容(含 key/value/headers 等)

  • 绝对 Offset:8 字节长整型,表示消息在分区中的全局序列号

  • Position:4 字节整型,记录该消息在文件中的物理起始位置

  • 示例:[MessageA][offset=1000][position=0] → [MessageB][offset=1001][position=1024]

  • 索引文件优化设计

  • 采用紧凑的二进制结构,每条索引记录包含:

  • 相对 Offset:4 字节(存储与当前 segment 第一条消息的 offset 差值)

  • Position:4 字节(对应消息在.log 文件中的物理位置)

  • 例如 segment 起始 offset 为 1000 时:

  • 绝对 offset 1005 → 存储为相对 offset 5

  • 节省 50%存储空间(相比 8 字节绝对 offset)

  • 查询时自动完成相对/绝对 offset 转换,对客户端完全透明

  • 典型查询流程

  • 客户端请求 offset=1005 的消息

  • 二分查找.index 文件找到最近条目(相对 offset=5)

  • 根据 position 定位.log 文件位置

  • 顺序扫描找到绝对 offset=1005 的消息


稀疏索引的密度不高,但是 Offset 有序,二分查找的时间复杂度为 O(LogN),如果从头遍历时间复杂度是 O(N)如下图:



偏移量索引由相对偏移量和物理地址组成:



可以通过下面的命令解析 .index 文件:


kafka-run-class.sh kafka.tools.DumpLogSegments --files 00000000000000000000.index --print-data-log | head
复制代码


注意:Offset 与 Position 没有直接关系,因为会删除数据和清理日志注意:Offset 与 Position 没有直接关系,因为会删除数据和清理日志注意:Offset 与 Position 没有直接关系,因为会删除数据和清理日志


执行结果如下图所示:



在偏移量索引文件索引中,索引数据都是顺序记录 Offset,但时间戳索引文件中每个追加的索引时间戳必须大于之前追加的索引项,否则不予追加。在 Kafka 0.11.0.0 以后,消息元数据中存在若干的时间戳信息。如果 Broker 端参数 log.message.timestamp.type 设置为 LogAppendTime,那么时间戳必定能保持单调增长。反之如果是 CreateTime 则无法保证顺序。


注意:timestamp 文件中的 Offset 与 Index 文件中的 relativeOffset 不是一一对应的,因为数据的写入是各自追加的注意:timestamp 文件中的 Offset 与 Index 文件中的 relativeOffset 不是一一对应的,因为数据的写入是各自追加的注意:timestamp 文件中的 Offset 与 Index 文件中的 relativeOffset 不是一一对应的,因为数据的写入是各自追加的

思考: 如何查看偏移量为 23 的消息?

Kafka 中存在一个 ConcurrentSkipListMap 来保存在每个日志分段中,通过跳跃表方式,定位到 00000000000000000000.index,通过二分法在偏移量索引文件中找到不大于 23 的最大索引项,即 Offset 20 那栏,然后从日志分段文件中的物理位置为 320 开始顺序查找偏移量为 23 的消息。

时间戳

在偏移量索引文件中,索引数据都是顺序记录 Offset,但时间戳索引文件中每个追加的索引时间戳必须大于之前追加的索引项,否则不予追加。在 Kafka 0.11.0.0 以后,消息信息中存在若干的时间戳消息。如果 Broker 端参数 log.message.timestamp.type 设置为 LogAppendTime,那么时间戳必定能保持单调增长。反之如果是 CreateTime 则无法保证顺序。


通过时间戳方式进行查找消息,需要通过查找时间戳索引和偏移量索引两个文件。时间戳索引格式:前八个字节表示时间戳,后四个字节表示偏移量。



思考: 查找指定时间戳开始的消息?

假设某个时间戳为 A


  • 查找时间戳 A 应该在哪个日志分段中,将 A 和每个日志分段中最大时间戳 LargestTimestamp 逐一对比,直到找到不小于 A 所对应的日志分段。

  • 日志分段中的 LargestTimeStamp 的计算是:先查询该日志分段所对应时间戳索引文件,找到最后一条索引项,若最后一条索引项的时间戳字段值大于 0,则取该值,否则取该日志分段的最近修改时间。

  • 查找该日志分段的偏移量索引文件,查找该偏移量对应的物理地址。

  • 日志文件中从 320 的物理位置开始查找小于 A 的数据。

发布于: 刚刚阅读数: 2
用户头像

武子康

关注

永远好奇 无限进步 2019-04-14 加入

Hi, I'm Zikang,好奇心驱动的探索者 | INTJ / INFJ 我热爱探索一切值得深究的事物。对技术、成长、效率、认知、人生有着持续的好奇心和行动力。 坚信「飞轮效应」,相信每一次微小的积累,终将带来深远的改变。

评论

发布
暂无评论
大数据-69 Kafka 存储结构解析:日志文件与索引文件的内部机制_Java_武子康_InfoQ写作社区