说说kafka日志文件

1,620 阅读11分钟

1.前言

系统仅仅拥有高并发、高可用往往是不够的,在此基础上还需要具备高可靠性。那么什么是高可靠性呢?高可靠性就是系统在宕机恢复后数据不丢失,仍然可以保证业务的正常运行。

作为业务系统的开发人员,提到数据持久化,基于本能反应首选想到的一定是数据库kafka作为消息中间件,会选择什么方式来进行数据的持久化呢?答案是:日志文件

2.日志文件内容

既然知道kafka采用日志文件的方式来对数据进行持久化,想你一定会好奇,日志文件中到底存储的是什么内容?

2.1 如何找到日志文件位置

想看日志文件内容,首先你得知道日志文件路径,通过config目录下server.properties配置文件中的log.dirs=配置项可以确定日志文件存放路径

确定配置文件路径后,切换到对应目录,依据topic + partition来确定目标目录,比如想查看topic 为product,0号分区对应的日志文件,需要找到product-0 目录即可

进入目标目录,查看目录下的文件信息

-rw-r--r--  1   1137481367        24  9  3 16:02 00000000000000000000.index
-rw-r--r--  1   1137481367     16375  9  3 16:02 00000000000000000000.log
-rw-r--r--  1   1137481367        48  9  3 16:02 00000000000000000000.timeindex
-rw-r--r--  1   1137481367        24  9  3 16:02 00000000000000002186.index
-rw-r--r--  1   1137481367     16224  9  3 16:02 00000000000000002186.log
-rw-r--r--  1   1137481367        48  9  3 16:02 00000000000000002186.timeindex
-rw-r--r--  1   1137481367        24  9  3 16:02 00000000000000004415.index
-rw-r--r--  1   1137481367     16275  9  3 16:02 00000000000000004415.log
-rw-r--r--  1   1137481367        48  9  3 16:02 00000000000000004415.timeindex
-rw-r--r--  1   1137481367        24  9  3 16:02 00000000000000006647.index
-rw-r--r--  1   1137481367     16278  9  3 16:02 00000000000000006647.log
-rw-r--r--  1   1137481367        48  9  3 16:02 00000000000000006647.timeindex
-rw-r--r--  1   1137481367  10485760  9  3 16:02 00000000000000008879.index
-rw-r--r--  1   1137481367      8594  9  3 16:02 00000000000000008879.log
-rw-r--r--  1   1137481367  10485756  9  3 16:02 00000000000000008879.timeindex

通过文件信息展示可看出kafka日志中会有三种文件,分别以.index.log.timeindex结尾,从名字上可以看出.index.timeindex应该与索引相关,.log文件才是真正存储数据的地方

2.2 查看日志文件内容

明确存储数据文件后,你就可以通过vi命令打开.log文件查看具体内容,不出意外,映入眼帘的是满屏看不懂、不明所以的玩意

看不懂,那一定是打开的方式不对,千万不要怀疑是自己能力的问题,你可以尝试使用如下方式打开文件

bin/kafka-run-class.sh kafka.tools.DumpLogSegments --deep-iteration --print-data-log --files /xxxxx/kafka/kafka_2.12-3.2.1/logs/product-0/00000000000000000000.log

使用正确方式打开文件内容

baseOffset: 0 lastOffset: 121 count: 122 baseSequence: -1 lastSequence: -1 producerId: -1 producerEpoch: -1 partitionLeaderEpoch: 0 isTransactional: false isControl: false deleteHorizonMs: OptionalLong.empty position: 0 CreateTime: 1662192149996 size: 966 magic: 2 compresscodec: gzip crc: 3650920144 isvalid: true
| offset: 0 CreateTime: 1662192149978 keySize: -1 valueSize: 88 sequence: -1 headerKeys: [__TypeId__] payload: "{"producerId":"strimzi-canary-client","messageId":1,"timestamp":1662192149651}"
...
| offset: 121 CreateTime: 1662192149996 keySize: -1 valueSize: 90 sequence: -1 headerKeys: [__TypeId__] payload: "{"producerId":"strimzi-canary-client","messageId":122,"timestamp":1662192149996}"
​
​
baseOffset: 122 lastOffset: 243 count: 122 baseSequence: -1 lastSequence: -1 producerId: -1 producerEpoch: -1 partitionLeaderEpoch: 0 isTransactional: false isControl: false deleteHorizonMs: OptionalLong.empty position: 966 CreateTime: 1662192150002 size: 936 magic: 2 compresscodec: gzip crc: 635681227 isvalid: true
| offset: 122 CreateTime: 1662192149996 keySize: -1 valueSize: 90 sequence: -1 headerKeys: [__TypeId__] payload: "{"producerId":"strimzi-canary-client","messageId":123,"timestamp":1662192149996}"
...
| offset: 243 CreateTime: 1662192150002 keySize: -1 valueSize: 90 sequence: -1 headerKeys: [__TypeId__] payload: "{"producerId":"strimzi-canary-client","messageId":244,"timestamp":1662192150002}"
​
​
baseOffset: 244 lastOffset: 365 count: 122 baseSequence: -1 lastSequence: -1 producerId: -1 producerEpoch: -1 partitionLeaderEpoch: 0 isTransactional: false isControl: false deleteHorizonMs: OptionalLong.empty position: 1902 CreateTime: 1662192150007 size: 905 magic: 2 compresscodec: gzip crc: 2330978284 isvalid: true
| offset: 244 CreateTime: 1662192150002 keySize: -1 valueSize: 90 sequence: -1 headerKeys: [__TypeId__] payload: "{"producerId":"strimzi-canary-client","messageId":245,"timestamp":1662192150002}"
...
| offset: 365 CreateTime: 1662192150007 keySize: -1 valueSize: 90 sequence: -1 headerKeys: [__TypeId__] payload: "{"producerId":"strimzi-canary-client","messageId":366,"timestamp":1662192150007}"

通过内容展示方式可以看到日志文件中记录了每条消息对应的offset创建时间以及消息内容

再打开00000000000000002186.log查看,可以得出一个结论,那就是文件名对应了该文件中第一条消息的offset

3.性能

3.1 顺序写

数据写入磁盘是一种比较耗时操作,以至于看到该类操作的第一反应就是慢,能避免就尽量避免,但是为了保证数据的可靠性,这种操作往往又是不可或缺的

其实这里往往有一个误区,所有的IO操作都比较慢嘛?当然不是,顺序IO虽没有内存操作快,但是也不差

kafka也是通过采用顺序写IO方式来实现数据写盘,从而提升写性能

3.2 分而治之

不知你是否有过类似的经历,打开一个数兆文件只需要几秒时间,但是打开一个数百兆或者更大文件却需要几分钟甚至更长时间

由此可以想象一下,如果不断往.log文件中追加消息,那么随着时间的推移.log文件会变得越来越大,大到打开该文件需要花费分钟级别的耗时,这对高性能kafka是完全不能接受的一件事情,那么如何解决大文件带来的耗时问题?

分而治之是一种很好的思想,我们可以将一个大文件拆分成由多个小文件组成,从而解决大文件带来的性能问题

3.3 索引查询

现在你已经知道kafka会将客户端推送过来的消息存放在.log文件中,那么kafka是如何检索指定offset的消息给消费者进行消费的呢?

不知你是否会想到前文提到的.index文件,通过如下命令来看看文件中存储的内容

bin/kafka-run-class.sh kafka.tools.DumpLogSegments --print-data-log --files /xxx/kafka/kafka_2.12-3.2.1/logs/product-0/00000000000000000000.index
offset: 731 position: 4623
offset: 1338 position: 9121
offset: 1943 position: 13634

该文件存储了offsetposition映射关系,到此可以得出根据offset就能找到对应的position的结论

看完.index文件后,再来看看主角.log文件

baseOffset: 0 lastOffset: 121 count: 122 position: 0 CreateTime: 1662192149996 size: 966 magic: 2 compresscodec: gzip crc: 3650920144 isvalid: true
| offset: 0 CreateTime: 1662192149978 keySize: -1 valueSize: 88 sequence: -1 headerKeys: [__TypeId__] payload: "{"producerId":"strimzi-canary-client","messageId":1,"timestamp":1662192149651}"
......
| offset: 121 CreateTime: 1662192149996 keySize: -1 valueSize: 90 sequence: -1 headerKeys: [__TypeId__] payload: "{"producerId":"strimzi-canary-client","messageId":122,"timestamp":1662192149996}"
baseOffset: 122 lastOffset: 243 count: 122 position: 966 CreateTime: 1662192150002 size: 936 magic: 2 compresscodec: gzip crc: 635681227 isvalid: true
baseOffset: 244 lastOffset: 365 count: 122 position: 1902 CreateTime: 1662192150007 size: 905 magic: 2 compresscodec: gzip crc: 2330978284 isvalid: true
baseOffset: 366 lastOffset: 487 count: 122 position: 2807 CreateTime: 1662192150011 size: 903 magic: 2 compresscodec: gzip crc: 1412054061 isvalid: true
baseOffset: 488 lastOffset: 609 count: 122 position: 3710 CreateTime: 1662192150015 size: 913 magic: 2 compresscodec: gzip crc: 3677057634 isvalid: true
baseOffset: 610 lastOffset: 731 count: 122 position: 4623 CreateTime: 1662192150019 size: 903 magic: 2 compresscodec: gzip crc: 1937572371 isvalid: true
| offset: 610 CreateTime: 1662192150015 keySize: -1 valueSize: 90 sequence: -1 headerKeys: [__TypeId__] payload: "{"producerId":"strimzi-canary-client","messageId":611,"timestamp":1662192150015}"
......
| offset: 731 CreateTime: 1662192150019 keySize: -1 valueSize: 90 sequence: -1 headerKeys: [__TypeId__] payload: "{"producerId":"strimzi-canary-client","messageId":732,"timestamp":1662192150019}"baseOffset: 732 lastOffset: 853 count: 122 position: 5526 CreateTime: 1662192150022 size: 902 magic: 2 compresscodec: gzip crc: 677681648 isvalid: true
baseOffset: 854 lastOffset: 975 count: 122 position: 6428 CreateTime: 1662192150025 size: 880 magic: 2 compresscodec: gzip crc: 1374850199 isvalid: true
baseOffset: 976 lastOffset: 1096 count: 121 position: 7308 CreateTime: 1662192150027 size: 909 magic: 2 compresscodec: gzip crc: 2771471302 isvalid: true
baseOffset: 1097 lastOffset: 1217 count: 121 position: 8217 CreateTime: 1662192150030 size: 904 magic: 2 compresscodec: gzip crc: 438650712 isvalid: true
baseOffset: 1218 lastOffset: 1338 count: 121 position: 9121 CreateTime: 1662192150033 size: 898 magic: 2 compresscodec: gzip crc: 983033759 isvalid: true
​
baseOffset: 1339 lastOffset: 1459 count: 121 position: 10019 CreateTime: 1662192150035 size: 895 magic: 2 compresscodec: gzip crc: 964946264 isvalid: true
baseOffset: 1460 lastOffset: 1580 count: 121 position: 10914 CreateTime: 1662192150040 size: 941 magic: 2 compresscodec: gzip crc: 2189697033 isvalid: true
baseOffset: 1581 lastOffset: 1701 count: 121 position: 11855 CreateTime: 1662192150043 size: 903 magic: 2 compresscodec: gzip crc: 2220582279 isvalid: true
baseOffset: 1702 lastOffset: 1822 count: 121 position: 12758 CreateTime: 1662192150045 size: 876 magic: 2 compresscodec: gzip crc: 3714367387 isvalid: true
baseOffset: 1823 lastOffset: 1943 count: 121 position: 13634 CreateTime: 1662192150048 size: 901 magic: 2 compresscodec: gzip crc: 531545191 isvalid: true
​
baseOffset: 1944 lastOffset: 2064 count: 121 position: 14535 CreateTime: 1662192150050 size: 896 magic: 2 compresscodec: gzip crc: 3782783873 isvalid: true
baseOffset: 2065 lastOffset: 2185 count: 121 position: 15431 CreateTime: 1662192150055 size: 944 magic: 2 compresscodec: gzip crc: 198975981 isvalid: true

根据.log文件内容,可以画出下图

结论:

1.根据指定的offset.index文件中找到对应的position

2.根据position.log文件中找到目标位置,如果查找目标offset大于position对应的offset,那么向后查找,否则向前查找,直到找到目标offset对应消息为止

4.总结

在了解完kafka消息存放形式以及查找方式后,我们能从中学到哪些内容呢?

1.针对大文件带来的读写问题,可以通过拆分文件的方式进行解决,类比elasticsearch以及redis分片,分而治之思想

2.针对查询慢的问题,可以通过创建索引方式进行解决,类比mysql索引、书本目录