Kafka_Kafka 的日志组织格式

 

参考文章 : 《深入理解Kafka 核心设计 与 实践原理 》 朱忠华 著

 

目录:

1.日志格式划分

  1.1 v0 版本

  1.2 v1 版本

  1.3 v2 版本

2. 各个版本消息格式变更

  2.1 v0 版本

  2.2 v1 版本

  2.3 v2 版本

3. v0 与 v1 的日志压缩

4. 日志在磁盘上的组织形式

   4.1 日志存放目录

   4.2 索引文件

      4.2.1  偏移量索引

      .2.2  时间戳索引

5.分析日志文件,索引文件

 

  我们学习了Kafka 的一些基本指令,也知道 Kafka 的日志是存储在磁盘上的。

那么 Kafka 的日志是如何组织的呢,我们也需要进一步了解一下。

 

目前最新的Kafka 版本是 2.0.0 ,我们的文章的测试环境是 Kafka 1.0.1。

 

我们来了解下 Kafka 的日志格式变更记录:

 

日志格式划分

 

V0 版本:

    Kafka 消息格式的第一个版本称为 v0 版本。在 Kafka 0.10.0 之前都采用这种消息格式。(在 0.8.x 之前, Kafka 还使用过一个更加古老的消息格式)。对于目前的 Kafka 而言,不需要了解这个版本的消息格式,如无特殊说明,我们只讨论未压缩的情形。

 

V1 版本:

  Kafka 从 0.10.0 版本开始到 0.11.0 版本之前所使用的消息格式为 v1, 比 v0 版本 多了一个 timestamp。v1 版本的 magic 字段的值为 1.

 

V2 版本:

   Kafka 从 0.11.0 版本开始使用的版本消息格式为 v2,  我们可以通过查看日志的 magic 信息,进行确认。

 

 

各个版本 消息形式 与 版本变更特性 

 

V0 版本 : 

  

V0 版本的格式如上图所示:

下面我们对这个日志格式进行讲解 :

 

offset :  每条消息都有一个 offset 用来标识 它在分区中的偏移量, 这个 offset 是逻辑值,而非实际物理偏移值。

message_size : 表示消息的大小。

LOG_OVERHEAD : offset + message_size 合在一起,表示 日志头部, 固定为 12 B.

MESSAGE : LOG_OVERHEAD + RECORD 一起描述 一条消息。

 

Message_set : 消息集,消息集包含一条 或 多条消息,消息集 不仅仅是 存储在 磁盘上,以及网络上传输 (Produce & Fetch )

的基本形式,而且是 Kafka 中 压缩的基本单元。

下面从 crc32 算起,各个字段的解释如下:

crc32 (4B): crc32 校验值。校验范围为 magic 至 value 之间。

magic (1B) : 消息格式版本号,此版本的 magic 为0。

attributes (1B) : 消息的属性。总共占1个字节,低3位表示压缩类型:0 表示NONE, 1 表示GZIP, 2 表示SNAPPY, 3 表示LZ4 ( LZ4 自Kafka 0.9.x 引入),其余位保留。

key length (4B): 表示消息的 key 的长度。如果为-1, 标识没有设置 key, 即 key == null.

Key: 可选 : 可选,如果没有 key 则无此字段

value length (4B)  实际消息体的长度。如果为 -1 ,则标识消息为空。

value :可选,可以为空,比如 tombstone 消息。

 

V1 版本:

Kafka 从 0.10.0 到 0.11.0 版本 之前所用的消息格式版本为 v1, 比 v0 版本多了 timestamp 字段。

v1 版本的 magic 字段为 1 。v1 版本的 attribute 字段的低三位 和 v0 版本一样,还是表示压缩类型,第4个位 bit 也被利用起来。

0 表示 timestamp 为 createTime

1 表示 timestamp 的类型为 LogAppendTime

timestamp 类型由broker 端参数 log.message.timestamp.type 来配置,默认值为 createTime, 即采用生产者创建消息时的时间戳。

 

 

 

V2 版本:

 

Kafka 从0.11.0 版本所用的消息格式为 v2, 这个版本的消息相比 v0 和 v1 版本变动很大,

参考 Protocol Buffer 引入了 变长整形 Varints 和 ZigZag 编码。

 

Varints 

Varints 是使用一个或多个字节来序列化整形的一种方法。数值越小,其占用的字节数就越少。Varints 中的每个字节都有一个位于最高位的 msb 位(most significant bit ),除最后一个字节外,其余 msb 位都设置为1 ,最后一个字节的 msb 位为0。这个 msb 位表示其后的字节是否和当前字节一起来表示 同一个整数。

除msb 位外,剩余7位用于存储数据本身,这种表示类型又称为 Base128.

 

ZigZag 编码

 ZigZag 编码以一种锯齿形 (zig-zags )的方式来回穿梭正负整数,将带符号整数映射为 无符号整数,将带符号整数 映射为 无符号整数,这样可以使绝对值较小的负数仍然享有较小的 Vaints 编码值,比如 -1 编码为 1, 1 编码为 2 ,-2 编码为 3

 

原值编码后的值
00
-11
12
-23
24
......

对应的公式为 

(n << 1) ^ ( n  >> 31)

 

-----------------------------------

 

v2 版本中的消息集 称为 Record Batch , 而不是先前的 Message Set,  其内部也包含了 一条 或 多条消息,消息的格式 参见 下图。在消息压缩的情形下, Record Batch Header 部分 是不被压缩的, 而被压缩的 是 records 字段中的 所有内容。

 

对于内部Record : 

 

  先来看下 Record 的关键字段,可以看到内部字段大量采用了 Varints 。这样 Kafka 可以根据具体的值来确定需要几个字节来保存。

v2 版本消息格式去掉了 crc 字段,另外增加了 length (消息总长度), timestamp delta( 时间戳增量), offset delta (位移增量) 和 headers 信息,并且 attributes 字段被弃用了,笔者对此做如下分析。

tips : key, key length, value ,value length 同 v0 和 v1 版本的一样,这里不再复述。

 

》》length : 消息总长度

》》attributes : 弃用,但还是在消息格式中占据 1B 的大小,以备未来的格式扩展

》》timestamp delta : 时间戳增量。通常一个 timestamp 需要占用8个字节,如果像这里一样保存 与 RecordBatch 的起始时间戳的差值,则可以进一步节省占用的字节数。

》》offset delta : 位移增量。保存与 RecordBatch 起始位移的差值,可以节省占用的字节数。

》》headers : 这个字段用来支持应用级别的扩展,而不需要像 v0 和 v1 版本一样不得不将一些应用级别的属性值嵌入消息体,Header 的格式如最右部分所示,包含 key 和 value . 一个Record 里面可以包含 0 至 多个 Header 。

额外:

  在 V2 版本中将 crc 字段 从Record 中转移到了 RecordBatch 中。

 

v2 版本对消息集 (RecordBatch)做了彻底的修改,除了提及的crc 字段,多了如下字段。

》  first offset : 表示当前 RecordBatch 的起始位移。

》 length : 计算从 partition leader epoch 字段开始到末尾的长度。

》 partition leder epoch : 分区 leader 纪元,可以看作分区 ledaer 的版本号 或更新次数

》 magic : 消息格式的版本号,对v2版本而言,magic 等于 2

》 attributes : 消息属性。注意这里占用了2个字节。

   低3位表示压缩格式,可以参考 v0 和 v1. 第4位表示时间戳类型,

   第5位表示此RecordBatch 是否处于 事务中, 0 表示非事务,1表示事务。

   第6位表示是否是 控制消息 (ControlBatch), 0 表示非控制消息,1表示是控制消息,控制消息用来支持事务功能。

控制消息用来支持事务功能。

》last offset delta : RecordBatch 中最后一个 Record 的 offset 与 first offset 的差值。主要被 broker 用来确保 RecordBatch 中 Record 组装的正确性。

》first timestamp : RecordBatch 中第一条 Record 的时间戳

》max timestamp : Record 中最大的时间戳,一般情况下是指最后一个 Record 的时间戳,和 last offset delta 的作用一样,用来确保消息组装的正确性。

》producer id : PID, 用来支持幂等和事务。

》producer epoch : 和 producer id 一样,用来支持幂等 和十五

》first sequence : 和 producer id,producer epoch 一样,用来支持幂等和事务

》records count : RecordBatch 中 Record 的个数。

 

 

===========================

 

v0 与 v1 的日志压缩

 

下面讲解下 V0 / V1 基于 message set 的日志压缩。

Kafka 日志中使用那种压缩方式 是通过参数 compression.type 进行配置的,默认值为  producer , 表示保留生产者使用的压缩方式。这个参数还可以配置为 ”gzip" , "snappy", "lz4", 分别对应 GZIP, SNAPPY, LZ4 这3种压缩算法。

如果参数 compression.tyoe 配置为 ”uncompressed“,  表示不压缩。

下面这两幅图表示 v0 / v1 下的压缩方式

                                                       图3

 

                                        

                                         图4

 

 当进行消息压缩时,是将整个消息集进行压缩作为内层消息 inner message,内层消息整体作为外层 (wrapper message)的 value. 压缩后的外层消息 (wrapper message )中的key 为 null, 所以图3 左半部分没有画出 key 字段,value 字段中保存的是多条压缩消息 (inner message, 内层消息),其中 Record 表示的是 从 crc32 到 value 的消息格式。

  当生产者创建压缩消息的时候,对内部压缩消息设置的 offset 从0开始为每个内部消息分配 offset 。(参考图 4)

  每个从生产者发出的消息集中的消息 offset 都是从0 开始的,当然这个 offset 不能直接存储在日志文件中,对 offset 的转换是在服务端进行的,客户端不需要做这个工作。

  外层消息保存了内部消息中最后一条消息的绝对位移(absolute offset),绝对位移是相对于整个分区而言的。

 

 

 

========================================================

 

4. 日志在磁盘上的组织形式

   4.1 日志存放目录

下面我们去磁盘上看下日志文件

我们在一个3节点的集群,创建了一个4分区,2备份的主题:

我们看下各个机器上的数据分布:

 

 

 

可以看到 topic 的各个 partition ,是按照文件夹的形式进行组织的。

cdh-manager 上分布了 0,2,3  等 3 个 partition

cdh-node1 上分布了 1,2,3 等 3个 partition

cdh-node2 上分不了 0,1 等2个 partition 

总计  3+ 3+ 2 = 8 ,8个partition , 由于是 2备份,所以 2 * 4 =8 , 与实际存储相同。

 

我们再去各个文件夹下面进一步的查看:

 

   4.2 索引文件

 

[root@cdh-node2 data]# cd test-topic-0/
[root@cdh-node2 test-topic-0]# ll
total 20488
-rw-r--r-- 1 kafka kafka 10485760 Apr  2 18:24 00000000000000000000.index
-rw-r--r-- 1 kafka kafka      155 Apr  2 20:17 00000000000000000000.log
-rw-r--r-- 1 kafka kafka 10485756 Apr  2 18:24 00000000000000000000.timeindex
-rw-r--r-- 1 kafka kafka       14 Apr  2 20:17 leader-epoch-checkpoint
[root@cdh-node2 test-topic-0]# 

 

可以看到主要有3种类型的文件,

1. 以 *.log 结尾的日志文件

2.以 *.index 结尾的 offset 索引 文件

3.以 *.timeindex 结尾的 time offset 时间索引 文件

 

前序 : 索引文件

  Kafka 中的索引文件 以 稀疏索引 (sparse index )的方式构造消息的索引,它并不保证每个消息在索引文件中都有对应的索引项。每当写入一定量 (由 broker 端参数 log.index.interval.bytes 指定),默认为 4096 ,即 4KB 的消息时,偏移量索引文件和时间戳索引文件分别增加一个偏移量索引项和时间戳索引项。

 

  日志分段文件打到一定的条件的时候需要进行切分,其对应的索引文件也会进行切分,日志分段文件切分满足以下条件之一就会进行切分。

1)当前日志分段文件的大小超过了 broker 端参数 log.segment.bytes 配置的值。log.segment.bytes 参数的默认值为 1073741824 ,即 1GB.

 

2)当前日志分段中消息的最大时间戳 与 当前系统的时间戳 差值小于 log.roll.ms 或者 log.roll.hours 参数配置的值。如果同时配置了 log.roll.ms 和 log.roll.hours 参数,那么 log.roll.ms 的优先级高。默认情况下,只配置了 log.roll.hours 参数,其值为168,即 7天。

 

3)偏移量索引文件或时间戳索引文件的大小达到broker 端参数 log.index.size.max.bytes 配置的值。log.index.size.max.bytes 的默认值为 10485760, 即 10MB.

 

4)追加的消息的偏移量 与 当前日志分段的 偏移量之间的差值 大于 Integer.MAX_VALUE, 即要追加的消息的偏移量不能转变为相对偏移量 (offset - baseOffset > Integer.MAX_VALUE )

 

 

      4.2.1  偏移量索引

 

  偏移量索引文件用来建立 消息偏移量 offset 到物理地址之间的映射关系,方便快速定位消息所在的物理文件位置。

偏移量索引项的格式如图所示,每个索引项占用8个字节,分为2个部分。

relativeOffset : 相对偏移量, 表示消息相对于 baseOffset 的偏移量,占用4个字节,当前索引文件的文件名即为baseOffset 的值。

position: 物理地址,也就是消息在日志分段文件中对应的物理位置,占用4个字节。

 

 

      4.2.2  时间戳索引

 

  时间戳索引文件,则根据指定的时间戳(timestamp) 来查找对应的偏移量信息。

 

每个索引项占用12个字节,分为2个部分。

timestamp : 当前日志分段最大的时间戳

relativeOffset : 时间戳所对应的消息的相对偏移量

时间戳索引文件中包含若干时间戳索引项,每个追加的时间戳索引项中的timestamp 必须大于之前追加的索引项的 timestamp, 否则不予追加。

如果 broker 段参数 log.message.timestamp.type 设置为 LogAppendTime, 那么消息的时间戳必定能够保持单调递增,相反,如果是 CreateTime 类型则无法保证。

与偏移量索引文件相似,时间戳索引文件大小必须是索引项大小 12B 的 整数倍,如果不满足条件也会进行裁剪。同样假设 broker 端参数 log.index.size.max.bytes 配置为 67, 那么对应于时间戳索引文件,Kafka 会在内部将其转为 60.

每当写入一定量的消息时,就会在偏移量索引文件和时间戳索引文件中,分别增加一个偏移量索引项和时间戳索引项,两个文件增加索引项的操作是同时进行的,但并不意味着偏移量索引中的relativeOffset 和 时间戳索引项 中的 relativeOffset 是同一个值。

 

 

5.分析日志文件,索引文件

 

我们已经知道日志存放目录下 会有 日志文件 与 索引文件 两种类型的文件,那么我们怎么分析它们呢。

这里我们可以借助官方的工具进行分析,在 kafka 2.0.0 可以执行如下的指令:

kafka-dump-log.sh --files /../../00000000000000000000000000.[index,timeindex,log] 进行分析

 

对于低于 kafka 2.0.0 的版本,我们可以利用其他工具进行分析:

我们可以使用 kafka-run-class.sh 进行分析,其实 kafka-dump-log.sh  就是 kafka-run-class.sh  的一个包装。

 

kafka-dump-log.sh 脚本内容如下:

exec $(dirname $0)/kafka-run-class.sh kafka.tools.DumpLogSegments "$@"

 

我们执行下脚本看一下都能得到什么样的内容

./kafka-dump-log.sh --files  /var/local/kafka/data/test-topic-0/00000000000000000000.log

[root@cdh-manager my_bin]# ./kafka-dump-log.sh --files  /var/local/kafka/data/test-topic-0/00000000000000000000.log
Dumping /var/local/kafka/data/test-topic-0/00000000000000000000.log
Starting offset: 0
baseOffset: 0 lastOffset: 0 baseSequence: -1 lastSequence: -1 producerId: -1 producerEpoch: -1 partitionLeaderEpoch: 49 isTransactional: false position: 0 CreateTime: 1554207461068 isvalid: true size: 77 magic: 2 compresscodec: NONE crc: 2649489883
baseOffset: 1 lastOffset: 1 baseSequence: -1 lastSequence: -1 producerId: -1 producerEpoch: -1 partitionLeaderEpoch: 50 isTransactional: false position: 77 CreateTime: 1554207473208 isvalid: true size: 78 magic: 2 compresscodec: NONE crc: 212389945

 

./kafka-dump-log.sh --files  /var/local/kafka/data/test-topic-0/00000000000000000000.index

[root@cdh-manager my_bin]# ./kafka-dump-log.sh --files  /var/local/kafka/data/test-topic-0/00000000000000000000.index
Dumping /var/local/kafka/data/test-topic-0/00000000000000000000.index
offset: 0 position: 0

 

./kafka-dump-log.sh --files  /var/local/kafka/data/test-topic-0/00000000000000000000.timeindex

[root@cdh-manager my_bin]# ./kafka-dump-log.sh --files  /var/local/kafka/data/test-topic-0/00000000000000000000.timeindex 
Dumping /var/local/kafka/data/test-topic-0/00000000000000000000.timeindex
timestamp: 0 offset: 0
Found timestamp mismatch in :/var/local/kafka/data/test-topic-0/00000000000000000000.timeindex
  Index timestamp: 0, log timestamp: 1554207461068
  Index timestamp: 0, log timestamp: 1554207461068
Found out of order timestamp in :/var/local/kafka/data/test-topic-0/00000000000000000000.timeindex
  Index timestamp: 0, Previously indexed timestamp: 0

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值