Kafka文件存储机制

419 阅读5分钟
原文链接: segmentfault.com

一,存储文件结构

  • topic: 可以理解为一个消息队列的名字
  • partition:为了实现扩展性,一个非常大的topic可以分布到多个 broker(即服务器)上,一个topic可以分为多个partition,每个partition是一个有序的队列
  • segment:partition物理上由多个segment组成
  • message:每个segment文件中实际存储的每一条数据就是message
  • offset:每个partition都由一系列有序的、不可变的消息组成,这些消息被连续的追加到partition中,partition中的每个消息都有一个连续的序列号叫做offset,用于partition唯一标识一条消息

1582190686836.png

二,Partition分区

本例中topic名称为test-topic,默认设置partition为3,topic创建成功后默认的存储位置在:/tmp/kafka-logs下,分区分别以topic名称-分区数命名,(不考虑副本的情况)如下:

//分布在不同的broker节点上  
test-topic-0  
test-topic-1  
test-topic-2

疑问一:为什么要分区呢?

为了性能考虑,如果不分区每个topic的消息只存在一个broker上,那么所有的消费者都是从这个broker上消费消息,那么单节点的broker成为性能的瓶颈,如果有分区的话生产者发过来的消息分别存储在各个broker不同的partition上,这样消费者可以并行的从不同的broker不同的partition上读消息,实现了水平扩展。

疑问二:分区文件下到底存了那些东西?

如下,其实每个分区下保存了很多文件,而概念上我们把他叫segment,即每个分区都是又多个segment构成的,其中index(索引文件),log(数据文件),time index(时间索引文件)统称为一个segment。
test-topic-0  
├── 00000000000000000001.index  
├── 00000000000000000001.log  
├── 00000000000000000001.timeindex  
├── 00000000000000001018.index  
├── 00000000000000001018.log  
├── 00000000000000001018.timeindex  
├── 00000000000000002042.index  
├── 00000000000000002042.log  
├── 00000000000000002042.timeindex

疑问一: 为什么有了partition还需要segment ?

通过上面目录显示存在多个segment的情况,既然有分区了还要存多个segment干嘛?如果不引入segment,那么一个partition只对应一个文件(log),随着消息的不断发送这个文件不断增大,由于kafka的消息不会做更新操作都是顺序写入的,如果做消息清理的时候只能删除文件的前面部分删除,不符合kafka顺序写入的设计,如果多个segment的话那就比较方便了,直接删除整个文件即可保证了每个segment的顺序写入

三,Segment存储

Segment中核心文件是index索引文件和log数据文件,既然是索引文件当然是为了更高效的定位到数据,那么索引文件和数据文件中到底是存了那些数据?又是如何快速找到消息数据呢?

3.1 使用kafka自带脚本发送测试数据

sh kafka-producer-perf-test.sh --topic test-topic --num-records 50000000 --record-size 1000 --throughput 10000000  --producer-props bootstrap.servers=192.168.60.201:9092

3.2 使用kafka自带脚本Dump index

sh kafka-run-class.sh kafka.tools.DumpLogSegments --files /tmp/kafka-logs/test-topic-0/00000000000000001018.index --print-data-log
offset: 1049 position: 16205  
offset: 1065 position: 32410  
offset: 1081 position: 48615  
offset: 1097 position: 64820  
offset: 1113 position: 81025  
offset: 1129 position: 97230

通过dump index我们发现其实索引文件中其实就保存了offset和position,分别是消息的offset也就是具体那一条消息,position表示具体消息存储在log中的物理地址。

疑问一:通过上面数据可以看出,kafka并不是每个offset都保存了,每隔6个offset存储一条索引数据,为什么在index文件中这些offset编号不是连续的呢?

因为index文件中并没有为数据文件中的每条消息都建立索引,而是采用了稀疏存储的方式,每隔一定字节的数据建立一条索引。这样避免了索引文件占用过多的空间,从而可以将索引文件保留在内存中。但缺点是没有建立索引的Message也不能一次定位到其在数据文件的位置,从而需要做一次顺序扫描,但是这次顺序扫描的范围就很小了。

3.3 使用kafka自带脚本Dump log

sh kafka-run-class.sh kafka.tools.DumpLogSegments --files /tmp/kafka-logs/test-topic-0/00000000000000001018.log --print-data-log

1582092826812.png

log数据文件中并不是直接存储数据,而是通过许多的message组成,message包含了实际的消息数据

关键字 解释说明
8 byte offset 在parition(分区)内的每条消息都有一个有序的id号,这个id号被称为偏移(offset),它可以唯一确定每条消息在parition(分区)内的位置。即offset表示partiion的第多少message
4 byte message size message大小
4 byte CRC32 用crc32校验message
1 byte “magic” 表示本次发布Kafka服务程序协议版本号
1 byte “attributes” 表示为独立版本、或标识压缩类型、或编码类型
4 byte key length 表示key的长度,当key为-1时,K byte key字段不填
K byte key 可选
value bytes payload 表示实际消息数据

疑问一:消费者如何通过offset查找message?

假如我们想要读取offset=1066的message,需要通过下面2个步骤查找。

  1. 查找segment file
    00000000000000000000.index表示最开始的文件,起始偏移量(offset)为0.第二个文件00000000000000001018.index的消息量起始偏移量为1019 = 1018 + 1.同样,第三个文件00000000000000002042.index的起始偏移量为2043=2042 + 1,其他后续文件依次类推,以起始偏移量命名并排序这些文件,只要根据offset 二分查找文件列表,就可以快速定位到具体文件。 当offset=1066时定位到00000000000000001018.index|log
  2. 通过segment file查找message
    通过第一步定位到segment file,当offset=1066时,依次定位到00000000000000001018.index的元数据物理位置和00000000000000001018.log的物理偏移地址,此时我们只能拿到1065的物理偏移地址,然后再通过00000000000000368769.log顺序查找直到offset=1066为止。每个message都有固定的格式很容易判断是否是下一条消息

1582270240253.png