1. 引言
在kafka中的数据最终会被持久化在每个broker上,kafka通过磁盘顺序写以及零拷贝(Zero-Copy)极大提高了数据持久化的速度。
关于磁盘写入,在kafka的官方文档中也提到,在具有六个7200rpm SATA RAID-5阵列的磁盘上,线性写入的性能约为600MB /秒,但随机写入的性能仅为约100k /sec,相差超过6000倍。所以,磁盘的性能完全取决于我们的使用方式。kafka使用文件的追加的方式实现数据
另外,kafka还使用了操作系统的页缓存(page cache),并没有使用java堆存储的方式,这是由于java内存的使用往往会付出更高的代价(GC和容量)。
2. 数据存储
2.1 目录结构
上一篇 整理讲述了kafka的一些关键概念,知道了kafka的消息以主题(topic)为单位进行隔离的。在数据存储中,每个主题又划分了多个分区(partition),主题+分区会在每个broker以文件夹的形式存在,上一篇 中我贴了一张图:
可以看到,首先创建了一个名为 mytopic 的主题,并且分配了3个分区,最终在broker的日志存储目录中创建了3个文件夹,名称分别为“<主题名>+<分区序号>”。每个文件夹内包含了日志索引文件(“.index”和“.timeindex”)和日志数据文件(“.log”)两部分。
2.2 数据文件存储结构
在每个partition中,包含了很多个LogSegment,LogSegment是由一个日志文件和两个索引文件组成。每个LogSegment的大小相等(大小可以在config/server.properties中通过 og.segment.bytes 属性配置,默认为1073741824字节,即1GB)。
LogSegment命名规则:第一个segment从0开始,后续每个segment文件名为上一个segment文件最后一条消息的offset, offset的数值最大为64位(long类型),20位数字字符长度,没有数字用0填充。
下面这张图很直观的表明了每个partition中的数据存储方式(图片出处):
2.3 索引文件
kafka的.log数据文件大小1G,如果没有索引,每次读取数据时都需要从文件头开始读,这样未免效率太低了。所以出现了以 .index 为后缀名的索引文件。每个索引条目由offset和position组成,offset表示每条消息的逻辑偏移量,postition标识消息在.log数据文件中实际的物理偏移量。这样,每个索引条目就可以唯一确定在各个分区数据文件的一条消息。其中,Kafka采用稀疏索引存储的方式,默认是日志写入大小达到4KB时,才会在.index中增加一个索引项。可以通过 log.index.interval.bytes 来设置这个间隔大小。
(图片出处)
.timeindex 索引是在 kafka 0.10.1.0 版本中新增的基于时间的索引文件,他包含两个字段 时间戳和 消息偏移量,它的作用则是为了解决根据时间戳快速定位消息所在位置。
我们可以通过如下命令将二进制分段的索引和日志数据文件内容转换为字符型文件:
- .log文件
./bin/kafka-run-class.sh kafka.tools.DumpLogSegments --files /tmp/kafka-logs/mytopic-0/000000000000000000.log --print-data-log > ~/000000000000000000_txt.log
- .index文件
./bin/kafka-run-class.sh kafka.tools.DumpLogSegments --files /tmp/kafka-logs/mytopic-0/000000000000000000.index --print-data-log > ~/000000000000000000_txt.index
- .timeindex文件
./bin/kafka-run-class.sh kafka.tools.DumpLogSegments --files /tmp/kafka-logs/mytopic-0/000000000000000000.timeindex --print-data-log > ~/000000000000000000_txt.timeindex
2.4 消息结构
kafka的消息结构经过了多次版本的调整,关于这部分最好还是参考官方文档,这里只简单说一下,列一些关键属性。
为了保证传输效率,生产者会将多条消息压缩到一起批量发送到broker,在broker中存储的消息也是压缩后的,最终消息会在消费端解压消费。最新版本的kafka中,被压缩在一起的一批消息被称为 Record Batch ,在 Record Batch中包含了多条 Record,大概的组织方式就像上图我们dump出来的.log文件一样。
这里简要的列举一下关键属性:
| 消息属性 | 属性说明 |
|---|---|
| baseOffset | 这一批消息中起始消息的offset |
| lastOffset | 这一批消息中结束消息的offset |
| count | 这一批消息中的消息总数 |
| size | 这一批消息的总大小 |
| magic | 消息格式的版本号 |
| crc32 | crc32校验值。校验范围为crc32之后字节。 |
| offset | 当前消息在partition的逻辑偏移量 |
| CreateTime | 时间戳 |
| keysize | 当前消息key大小 |
| valuesize | 当前消息value大小 |
| key | 消息的key |
| payload | 消息数据 |