文章目录
1. 消息的文件存储机制
我们知道一个 topic 的多个 partition 在物理磁盘上保存在 log.dirs
配置的路径,默认是/tep/kafka-logs/
。那么我们再来分析日志的存储方式。通过如下命令找到对应 partition下的日志内容:
[dwjf321@hadoop103 ~]$ ll /opt/module/kafka/logs/topic_start-0/
总用量 356
-rw-rw-r--. 1 dwjf321 dwjf321 176 1月 2 14:32 00000000000000018519.index.deleted
-rw-rw-r--. 1 dwjf321 dwjf321 348075 12月 24 23:41 00000000000000018519.log.deleted
-rw-rw-r--. 1 dwjf321 dwjf321 180 1月 2 14:32 00000000000000018519.timeindex.deleted
-rw-rw-r--. 1 dwjf321 dwjf321 10485760 1月 2 14:32 00000000000000019534.index
-rw-rw-r--. 1 dwjf321 dwjf321 0 1月 2 14:32 00000000000000019534.log
-rw-rw-r--. 1 dwjf321 dwjf321 10 12月 24 23:58 00000000000000019534.snapshot
-rw-rw-r--. 1 dwjf321 dwjf321 10485756 1月 2 14:32 00000000000000019534.timeindex
-rw-rw-r--. 1 dwjf321 dwjf321 13 1月 2 14:32 leader-epoch-checkpoint
kafka 是通过分段的方式将 Log 分为多个 LogSegment。LogSegment 是一个逻辑上的概念,一个 LogSegment 对应磁盘上的一个日志文件和一个索引文件,其中日志文件是用来记录消息的。索引文件是用来保存消息的索引。那么这个 LogSegment 是什么呢?
2. LogSegment
假设 kafka 以 partition 为最小存储单位,那么我们可以想象当 kafka producer 不断发生消息,必然会引起 partition 文件的无限扩张,这样对于消息文件的维护以及被消费的消息的清理带来非常大的挑战,所以 kafka 以 segment 为单位又把 partition 进行细分。每个 partition 相当于一个巨型文件被平均分配到多个大小相等的 segment 数据文件中(每个 segment 文件中的消息不一定相等),这种特性方便已经被消费的消息的清理,提高磁盘的利用率。
log.segment.bytes=107370
,设置分段大小,默认是 1 G,我们把这个值调小后,可以看到日志分段的效果。
抽取其中 3 个分段来进行分析
segment 文件由 2 大部分组成,分别为索引文件和数据文件 。这 2 个文件一一对应,成对出现,后缀 .index
和 .log
分别表示 索引文件和数据文件。
segment 文件命名规则:
partition 全局的第一个 segment 从 0 开始,后续每个 segment 文件名为上一个 segment 文件最后一条消息的 offset 值进行递增。数组最大为 64 位 long 大小。20 位数字字符长度,没有数字用 0 填充。
3. 查看 segment 文件命名规则
可以通过下面这条命令查看消息日志的内容
[dwjf321@hadoop103 ~]$ bin/kafka-run-class.sh kafka.tools.DumpLogSegments --files logs/topic_start-0/00000000000000000000.log --print-data-log
内容如下:
offset: 5376 position: 102124 CreateTime: 1531477349287isvalid:true keysize:-1 valuesize: 12 magic: 2compresscodec: NONE producerId:-1 producerEpoch:-1 sequence:-1 isTransactional: false headerKeys: [] payload: message_5376
第一个 log 文件的最后一个offset为:5376,所以下一个 segment 的文件命名:00000000000000005376.log
。对应的 index 为00000000000000005376.index
4. segment 中 index 和 log 的对应关系
从所有分段中,找一个分段进行分析:
为了提高查找消息的性能,为每一个日志文件添加 2 个所以文件:OffsetIndex 和 TimeIndex,分别对应*.index
和 *.timeindex
。TimeIndex 索引文件格式:它是映射时间戳和相对 offset。
查看索引内容:
[dwjf321@hadoop103 ~]$ bin/kafka-run-class.sh kafka.tools.DumpLogSegments --files logs/topic_start-0/00000000000000000000.index --print-data-log
如图所示,index 中存储了索引以及物理偏移量。log 存储消息的内容。索引文件的元数据执行对应数据文件中 message 的物理偏移地址。举个简单的案例来说,以[4053,80899]
为例,在 log 文件中,对应的是第 4053 条记录,物理偏移量(position)为 80899。position 是 ByteBuffer 的指针位置。
5. 在 partition 中如何通过 offset 查找 message
查找算法是:
- 根据 offset 的值,查找 segment 端中的 index 索引文件。由于索引文件命名是以上一个文件的最后一个 offset 进行命名,所以使用二分查找算法你能够根据 offset 快速定位到指定的索引文件。
- 找到索引文件后,根据 offset 进行定位,找到索引文件中的符合范围的索引。(kafka 采用稀疏索引的方式来提高查找性能)。
- 得到 position 以后,再到对应的 log 文件中,从 position 处开始查找 offset 对应的消息,将每条消息的 offset 与目标 offset 进行比较,直到找到消息。
比如说,我们要找 offset=2490
这条消息,那么先找到 00000000000000000000.index
索引文件,然后找到 [2487,49111]
这个索引,再到 log 文件中,根据 4911 这个 position 开始查找,比较每条消息的 offset 是否大于等于 2490.最后查找到对应的消息后返回。
6. Log 文件的消息内容分析
前面我们通过 kafka 提供的命令,可以查看二进制的日志文件信息,一条消息包含很多的字段。
offset: 5376 position: 102124 CreateTime: 1531477349287 isvalid:true keysize:-1 valuesize: 12 magic: 2 compresscodec: NONE producerId:-1 producerEpoch:-1 sequence:-1 isTransactional: false headerKeys: [] payload: message_5376
-
offset
:消息偏移量 -
position
:消息物理偏移量 -
CreateTime
:创建时间 -
keysize
::key 的大小 -
valuesize
:value 的大小 -
compresscodec
:压缩编码 -
payload
:消息具体内容
7. 日志的清除策略
前面提到过,日志的分段存储:
- 能够减少单个文件内容的大小。
- 方便 kafka 进行日志清理。
日志的清理策略有两个:
- 根据消息的保留时间,当消息在 kafka 中保存的消息超过了指定时间就会触发清理过程。
- 根据 topic 存储的数据大小,当 topic 所占的日志文件大小大于一定阈值。则可以开始删除最旧的消息。kafka 会启动一个后台线程,定期检查是否存在可以删除的消息。
通过 log.retention.bytes
和 log.retention.hours
这两个参数来设置。当其中任意一个达到要求,都会执行删除。
默认保留的时间是:7天。
8. 日志的压缩策略
Kafka 还提供了日志压缩(Log Compaction)功能,通过这个功能可以有效的减少日志文件的大小,缓解磁盘紧张的情况。在很多实际场景中,消息的 key 和 value 的值之间的对应关系是不断变化的。就行数据库中的数据会不断被修改一样,消费者只关心 key 对应的最新的 value。因此,我们可以开启 kafka 的日志压缩功能,服务端会在后台启动 Cleaner 线程池,定期将相同的 key 进行合并,只保留最新的 value 值。日志的压缩原理是: