对于一个分布式的流数据处理平台,消息存储在哪里是极为重要的。那么kafka的数据存储在哪里呢?

先说结论:

kafka放弃的Java的堆存储,改为使用磁盘(使用文件系统和操作系统的页缓存),同时将随机写改为顺序写,建立在文件追加的基础上,极大提高io性能。

首先要知道几个概念

Partition:每个主题又被分成一个或者若干个分区(Partition)。每个分区在本地磁盘上对应一个文件夹,分区命名规则为主题名称后接“—”连接符,之后再接分区编号,分区编号从0开始至分区总数减-1;

LogSegment:每个分区又被划分为多个日志分段(LogSegment)组成,日志段是Kafka日志对象分片的最小单位;LogSegment算是一个逻辑概念,对应一个具体的日志文件(“.log”的数据文件)和两个索引文件(“.index”和“.timeindex”,分别表示偏移量索引文件和消息时间戳索引文件)组成;(5)Offset:每个partition中都由一系列有序的、不可变的消息组成,这些消息被顺序地追加到partition中。每个消息都有一个连续的序列号称之为offset—偏移量,用于在partition内唯一标识消息(并不表示消息在磁盘上的物理位置);

Message:消息是Kafka中存储的最小最基本的单位,即为一个commit log,由一个固定长度的消息头和一个可变长度的消息体组成;

每个分区(物理上是每个文件夹,里面有多个LogSegment,每个LogSegment都是由一个数据文件以及两个索引文件组成)

我们可以看一下分区文件夹里面得到内容示例:

java Kafka日志异步处理 kafka的日志文件在哪_分布式

Kafka中的消息存储在物理上是以一个或多个分区(Partition)构成,每个分区对应本地磁盘上的一个文件夹,每个文件夹内包含了日志索引文件(“.index”和“.timeindex”)和日志数据文件(“.log”)两部分

日志数据文件(.log)

实际保存生产者发送的数据文件。该文件以该段的基准偏移量左补齐0命名,文件后缀为“.log”。每一个message都有一个offset偏移量来唯一定位消息在分区的位置。这个偏移量是递增的。因此,一条消息就可以根据它自己的偏移量以及每一个log文件的起始offset以及最多存放多少消息,确定自己在哪一个log文件上。如图:

java Kafka日志异步处理 kafka的日志文件在哪_偏移量_02

 

因此,每次新产生消息,消息数据就会以追加的形式,追加到log文件中,实现了磁盘的顺序写,提高了io的效率。

偏移量索引文件(.index)

有了顺序写,还需要考虑读取的问题。在log文件中顺序读取偏移量显然太耗时间了。因此我们考虑建立索引。为了提高查找消息的效率,故而为分段后的每个日志数据文件均使用稀疏索引的方式建立索引,这样子既节省空间又能通过索引快速定位到日志数据文件中的消息内容。

索引文件如图:

java Kafka日志异步处理 kafka的日志文件在哪_java_03

从上面dump出来的偏移量索引内容可以看出,索引条目用于将偏移量映射成为消息在日志数据文件中的实际物理位置,每个索引条目由offset和position(实际在磁盘的物理位置)组成,每个索引条目可以唯一确定在各个分区数据文件的一条消息。

之后就可以根据偏移量,进行二分查找,确定实际偏移量消息的物理位置。

时间戳索引文件(.timeindex)

每一条索引条目都对应了一个8字节长度的时间戳字段和一个4字节长度的偏移量字段,其中时间戳字段记录的是该LogSegment到目前为止的最大时间戳,后面对应的偏移量即为此时插入新消息的偏移量。

java Kafka日志异步处理 kafka的日志文件在哪_java_04

 

简而言之就是根据时间来快速定位消息位置的索引

总结:

从全文来看,Kafka高效数据存储设计的特点在于以下几点:

(1)、Kafka把主题中一个分区划分成多个分段的小文件段,通过多个小文件段,就容易根据偏移量查找消息、定期清除和删除已经消费完成的数据文件,减少磁盘容量的占用;

(2)、采用稀疏索引存储的方式构建日志的偏移量索引文件,并将其映射至内存中,提高查找消息的效率,同时减少磁盘IO操作;

(3)、Kafka将消息追加的操作逻辑变成为日志数据文件的顺序写入,极大的提高了磁盘IO的性能;