kafka 元数据信息存储在哪里,如何查看
本文主要讲述以下两部分内容:
kafka 数据的存储方式;
kafka 如何通过 offset 查找 message。
1.前言
写介绍 kafka 的几个重要概念:
Broker:消息中间件处理结点,一个 Kafka 节点就是一个 broker,多个 broker 可以组成一个 Kafka 集群;
Topic:一类消息,例如 page view 日志、click 日志等都可以以 topic 的形式存在,Kafka 集群能够同时负责多个 topic 的分发;
Partition:topic 物理上的分组,一个 topic 可以分为多个 partition,每个 partition 是一个有序的队;
Segment:每个 partition 又由多个 segment file 组成;
offset:每个 partition 都由一系列有序的、不可变的消息组成,这些消息被连续的追加到 partition 中。partition 中的每个消息都有一个连续的序列号叫做 offset,用于 partition 唯一标识一条消息;
message:这个算是 kafka 文件中最小的存储单位,即是 a commit log。
kafka 的 message 是以 topic 为基本单位,不同 topic 之间是相互独立的。每个 topic 又可分为几个不同的 partition,每个 partition 存储一部的分 message。topic 与 partition 的关系如下:
topic
其中,partition 是以文件夹的形式存储在具体 Broker 本机上。
2.partition 中的数据文件
有了上面的介绍,下面我们开始介绍 Topic 中 partition 的数据文件类型。
2.1.segment 中的文件
对于一个 partition(在 Broker 中以文件夹的形式存在),里面又有很多大小相等的 segment 数据文件(这个文件的具体大小可以在config/server.properties
中进行设置),这种特性可以方便 old segment file 的快速删除。
下面先介绍一下 partition 中的 segment file 的组成:
segment file 组成:由 2 部分组成,分别为 index file 和 data file,这两个文件是一一对应的,后缀”.index”和”.log”分别表示索引文件和数据文件;
segment file 命名规则:partition 的第一个 segment 从 0 开始,后续每个 segment 文件名为上一个 segment 文件最后一条消息的 offset,ofsset 的数值最大为 64 位(long 类型),20 位数字字符长度,没有数字用 0 填充。如下图所示:
segment
关于 segment file 中 index 与 data file 对应关系图,这里我们选用网上的一个图片,如下所示:
index
segment 的索引文件中存储着大量的元数据,数据文件中存储着大量消息,索引文件中的元数据指向对应数据文件中的 message 的物理偏移地址。以索引文件中的3,497
为例,在数据文件中表示第 3 个 message(在全局 partition 表示第 368772 个 message),以及该消息的物理偏移地址为 497。
注:Partition 中的每条 message 由 offset 来表示它在这个 partition 中的偏移量,这个 offset 并不是该 Message 在 partition 中实际存储位置,而是逻辑上的一个值(如上面的 3),但它却唯一确定了 partition 中的一条 Message(可以认为 offset 是 partition 中 Message 的 id)。
2.2.message 文件
message 中的物理结构为:
message
2.3.数据文件的内部实现方法
Partition 数据文件包含了若干上述格式的 message,按照 offset 由小到大排列在一起,它实现的类是 FileMessageSet,类图如下:
filemessageset
它的主要方法如下:
append: 把给定的 ByteBufferMessageSet 中的 Message 写入到这个数据文件中。
searchFor: 从指定的 startingPosition 开始搜索,找到第一个 Message 判断其 offset 是大于或者等于指定的 offset,并返回其在文件中的位置 Position。它的实现方式是从 startingPosition 开始读取 12 个字节,分别是当前 MessageSet 的 offset 和 size。如果当前 offset 小于指定的 offset,那么将 position 向后移动 LogOverHead+MessageSize(其中 LogOverHead 为 offset+messagesize,为 12 个字节)。
read:准确名字应该是 slice,它截取其中一部分返回一个新的 FileMessageSet。它不保证截取的位置数据的完整性。
sizeInBytes: 表示这个 FileMessageSet 占有了多少字节的空间。
truncateTo: 把这个文件截断,这个方法不保证截断位置的 Message 的完整性。
readInto: 从指定的相对位置开始把文件的内容读取到对应的 ByteBuffer 中。
3.查找
3.1.遇到的问题
我们首先试想一下,如果对于 Kafka 的一个 topic 而言,如果 topic 的 partition 中只有一个数据文件的话会怎么样?
新数据是添加在文件末尾(调用 FileMessageSet 的 append 方法),不论文件数据文件有多大,这个操作永远都是 O(1)的。
查找某个 offset 的 Message(调用 FileMessageSet 的 searchFor 方法)是顺序查找的。因此,如果数据文件很大的话,查找的效率就低。
3.2.如何去解决这个问题
由上述我们知道大数据培训,如果在 topic 的 partition 中只有一个数据文件的话,Kafka 插入的效率虽然很高,但是查找的效率非常低,那么 Kafka 在内部是如何解决查找效率的的问题呢?对于这个问题,Kafka 有两大法宝:分段和索引。
数据文件的分段
这个是比较好理解的,加入有 100 条 message,它们的 offset 是从 0 到 99,假设将数据文件分为 5 端,第一段为 0-19,第二段为 20-39,依次类推,每段放在一个单独的数据文件里面,数据文件以该段中最小的 offset 命名。这样在查找指定 offset 的 Message 的时候,用二分查找就可以定位到该 Message 在哪个段中。
为数据文件建索引
数据文件分段使得可以在一个较小的数据文件中查找对应 offset 的 message 了,但是这依然需要顺序扫描才能找到对应 offset 的 message。为了进一步提高查找的效率,Kafka 为每个分段后的数据文件建立了索引文件,文件名与数据文件的名字是一样的,只是文件扩展名为.index
。
索引文件中包含若干个索引条目,每个条目表示数据文件中一条 message 的索引。索引包含两个部分(均为 4 个字节的数字),分别为相对 offset 和 position。
相对 offset:因为数据文件分段以后,每个数据文件的起始 offset 不为 0,相对 offset 表示这条 message 相对于其所属数据文件中最小的 offset 的大小。举例,分段后的一个数据文件的 offset 是从 20 开始,那么 offset 为 25 的 message 在 index 文件中的相对 offset 就是 25-20 = 5。存储相对 offset 可以减小索引文件占用的空间。
position:表示该条 message 在数据文件中的绝对位置。只要打开文件并移动文件指针到这个 position 就可以读取对应的 message 了。
在 kafka 中,索引文件的实现类为 OffsetIndex,它的类图如下:
offsetindex
主要的方法有:
append 方法:添加一对 offset 和 position 到 index 文件中,这里的 offset 将会被转成相对的 offset。
lookup:用二分查找的方式去查找小于或等于给定 offset 的最大的那个 offset
3.3.通过 offset 查找 message
假如我们想要读取 offset=368776 的 message(见前面的第三个图),需要通过下面 2 个步骤查找。
查找 segment file
00000000000000000000.index 表示最开始的文件,起始偏移量(offset)为 0.第二个文件 00000000000000368769.index 的消息量起始偏移量为 368770 = 368769 + 1.同样,第三个文件 00000000000000737337.index 的起始偏移量为 737338=737337 + 1,其他后续文件依次类推,以起始偏移量命名并排序这些文件,只要根据 offset 二分查找文件列表,就可以快速定位到具体文件。
当 offset=368776 时定位到 00000000000000368769.index|log
通过 segment file 查找 message
通过第一步定位到 segment file,当 offset=368776 时,依次定位到 00000000000000368769.index 的元数据物理位置和 00000000000000368769.log 的物理偏移地址,然后再通过 00000000000000368769.log 顺序查找直到 offset=368776 为止。
segment index file 并没有为数据文件中的每条 message 建立索引,而是采取稀疏索引存储方式,每隔一定字节的数据建立一条索引,它减少了索引文件大小,通过 map 可以直接内存操作,稀疏索引为数据文件的每个对应 message 设置一个元数据指针,它比稠密索引节省了更多的存储空间,但查找起来需要消耗更多的时间。
总结:
Kafka 高效文件存储设计特点:
Kafka 把 topic 中一个 parition 大文件分成多个小文件段,通过多个小文件段,就容易定期清除或删除已经消费完文件,减少磁盘占用。
通过索引信息可以快速定位 message 和确定 response 的最大大小。
通过 index 元数据全部映射到 memory,可以避免 segment file 的 IO 磁盘操作。
通过索引文件稀疏存储,可以大幅降低 index 文件元数据占用空间大小。
评论