kafka日志存储

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


一、学前疑问

  1. 为什么要使用磁盘作为存储介质?
  2. 具体消息的存储格式又是什么呢?
  3. 怎么样能够快速检索到指定的消息?
  4. 消息不可能无限制存储,那么清理规则又是什么呢?

二、文件目录布局

Kafka 中的消息是以主题为基本单位进行归类的,各个主题在逻辑上相互独立。每个主题又可以分为一个或多个分区,分区的数量可以在主题创建的时候指定,也可以在之后修改。每条消息在发送的时候会根据分区规则被追加到指定的分区中,分区中的每条消息都会被分配一个唯一的序列号,也就是通常所说的偏移量(offset),如下图。
在这里插入图片描述
如果分区规则设置得合理,那么所有的消息可以均匀地分布到不同的分区中,这样就可以实现水平扩展。不考虑多副本的情况,一个分区对应一个日志(Log)。为了防止 Log 过大,Kafka又引入了日志分段(LogSegment)的概念,将Log切分为多个LogSegment,相当于一个巨型文件被平均分配为多个相对较小的文件,这样也便于消息的维护和清理。事实上,Log 和LogSegment 也不是纯粹物理意义上的概念,Log 在物理上只以文件夹的形式存储,而每个LogSegment 对应于磁盘上的一个日志文件和两个索引文件,以及可能的其他文件(比如以“.txnindex”为后缀的事务索引文件)。
Log对应了一个命名形式为<topic>-<partition>的文件夹。举个例子,假设有一个名为“topic-log”的主题,此主题中具有 4 个分区,那么在实际物理存储上表现为“topic-log-0”“topic-log-1”“topic-log-2”“topic-log-3”这4个文件夹:
在这里插入图片描述
向Log 中追加消息时是顺序写入的,只有最后一个 LogSegment 才能执行写入操作,在此之前所有的 LogSegment 都不能写入数据。为了方便描述,我们将最后一个 LogSegment 称为“activeSegment”,即表示当前活跃的日志分段。随着消息的不断写入,当activeSegment满足一定的条件时,就需要创建新的activeSegment,之后追加的消息将写入新的activeSegment。
为了便于消息的检索,每个LogSegment中的日志文件(以“.log”为文件后缀)都有对应的两个索引文件:偏移量索引文件(以“.index”为文件后缀)和时间戳索引文件(以“.timeindex”为文件后缀)。每个 LogSegment 都有一个基准偏移量 baseOffset,用来表示当前 LogSegment中第一条消息的offset。偏移量是一个64位的长整型数,日志文件和两个索引文件都是根据基准偏移量(baseOffset)命名的,名称固定为20位数字,没有达到的位数则用0填充。比如第一个LogSegment的基准偏移量为0,对应的日志文件为00000000000000000000.log。
举例说明,向主题topic-log中发送一定量的消息,某一时刻topic-log-0目录中的布局如下所示。
在这里插入图片描述示例中第2个LogSegment对应的基准位移是133,也说明了该LogSegment中的第一条消息的偏移量为133,同时可以反映出第一个LogSegment中共有133条消息(偏移量从0至132的消息)。
注意每个LogSegment中不只包含“.log”“.index”“.timeindex”这3种文件,还可能包含“.deleted”“.cleaned”“.swap”等临时文件,以及可能的“.snapshot”“.txnindex”“leader-epoch-checkpoint”等文件。
从更加宏观的视角上看,Kafka 中的文件不只上面提及的这些文件,比如还有一些检查点文件,当一个Kafka服务第一次启动的时候,默认的根目录下就会创建以下5个文件:
在这里插入图片描述消费者提交的位移是保存在 Kafka 内部的主题__consumer_offsets中的,初始情况下这个主题并不存在,当第一次有消费者消费消息时会自动创建这个主题。
在某一时刻。每一个根目录都会包含最基本的 4个检查点文件(xxx-checkpoint)和 meta.properties 文件。在创建主题的时候,如果当前 broker中不止配置了一个根目录,那么会挑选分区数最少的那个根目录来完成本次创建任务。

在这里插入图片描述

  • 三、日志版本

四、日志索引

每个日志分段文件对应了两个索引文件,主要用来提高查找消息的效率。偏移量索引文件用来建立消息偏移量(offset)到物理地址之间的映射关系,方便快速定位消息所在的物理文件位置;时间戳索引文件则根据指定的时间戳(timestamp)来查找对应的偏移量信息。

Kafka 中的索引文件以稀疏索引(sparse index)的方式构造消息的索引,它并不保证每个消息在索引文件中都有对应的索引项。每当写入一定量(由 broker 端参数 log.index.interval.bytes指定,默认值为4096,即4KB)的消息时,偏移量索引文件和时间戳索引文件分别增加一个偏移量索引项和时间戳索引项,增大或减小log.index.interval.bytes的值,对应地可以增加或缩小索引项的密度。
稀疏索引通过MappedByteBuffer将索引文件映射到内存中,以加快索引的查询速度。偏移量索引文件中的偏移量是单调递增的,查询指定偏移量时,使用二分查找法来快速定位偏移量的位置,如果指定的偏移量不在索引文件中,则会返回小于指定偏移量的最大偏移量。时间戳索引文件中的时间戳也保持严格的单调递增,查询指定时间戳时,也根据二分查找法来查找不大于该时间戳的最大偏移量,至于要找到对应的物理文件位置还需要根据偏移量索引文件来进行再次定位。稀疏索引的方式是在磁盘空间、内存空间、查找时间等多方面之间的一个折中。日志分段文件达到一定的条件时需要进行切分,那么其对应的索引文件也需要进行切分。日志分段文件切分包含以下几个条件,满足其一即可。

  1. 当前日志分段文件的大小超过了 broker 端参数 log.segment.bytes配置的值。log.segment.bytes参数的默认值1073741824,即1GB。
  2. 当前日志分段中消息的最大时间戳与当前系统的时间戳的差值大于 log.roll.mslog.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)。

对非当前活跃的日志分段而言,其对应的索引文件内容已经固定而不需要再写入索引项,所以会被设定为只读。而对当前活跃的日志分段(activeSegment)而言,索引文件还会追加更多的索引项,所以被设定为可读写。在索引文件切分的时候,Kafka 会关闭当前正在写入的索引文件并置为只读模式,同时以可读写的模式创建新的索引文件,索引文件的大小由broker端参数 log.index.size.max.bytes 配置。Kafka 在创建索引文件的时候会为其预分配log.index.size.max.bytes 大小的空间,注意这一点与日志分段文件不同,只有当索引文件进行切分的时候,Kafka 才会把该索引文件裁剪到实际的数据大小。也就是说,与当前活跃的日志分段对应的索引文件的大小固定为 log.index.size.max.bytes,而其余日志分段对应的索引文件的大小为实际的占用空间。

1. 偏移量索引


偏移量索引项的格式如图下图所示。每个索引项占用8个字节,分为两个部分。
(1)relativeOffset:相对偏移量,表示消息相对于baseOffset 的偏移量,占用4 个字节,当前索引文件的文件名即为baseOffset的值。
(2)position:物理地址,也就是消息在日志分段文件中对应的物理位置,占用4个字节。
在这里插入图片描述
消息的偏移量(offset)占用8个字节,也可以称为绝对偏移量。索引项中没有直接使用绝对偏移量而改为只占用4个字节的相对偏移量(relativeOffset=offset-baseOffset),这样可以减小索引文件占用的空间。举个例子,一个日志分段的 baseOffset 为 32,那么其文件名就是00000000000000000032.log,offset为35的消息在索引文件中的relativeOffset的值为35-32=3。
前面日志分段文件切分的第4个条件:追加的消息的偏移量与当前日志分段的偏移量之间的差值大于Integer.MAX_VALUE。如果彼此的差值超过了Integer.MAX_VALUE,那么relativeOffset就不能用4个字节表示了,进而不能享受这个索引项的设计所带来的便利了。
topic-log-0目录下的00000000000000000000.index为例进行具体分析,截取00000000000000000000.index部分内容如下:
在这里插入图片描述
虽然是以16进制数表示的,但参考索引项的格式可以知道如下内容:
在这里插入图片描述
这里也可以使用前面讲的kafka-dump-log.sh脚本来解析.index文件(还包括.timeindex、.snapshot、.txnindex等文件),示例如下:
在这里插入图片描述
单纯地讲解数字不免过于枯燥,我们这里给出 00000000000000000000.index 和00000000000000000000.log的对照图来做进一步的陈述
在这里插入图片描述
如果我们要查找偏移量为23的消息,那么应该怎么做呢?首先通过二分法在偏移量索引文件中找到不大于23的最大索引项,即[22,656],然后从日志分段文件中的物理位置656开始顺序查找偏移量为23的消息。
如果要查找偏移量为268的消息,那么应该怎么办呢?首先肯定是定位到baseOffset为251的日志分段,然后计算相对偏移量relativeOffset=268-251=17,之后再在对应的索引文件中找到不大于17的索引项,最后根据索引项中的position定位到具体的日志分段文件位置开始查找目标消息。那么又是如何查找baseOffset 为251的日志分段的呢?这里并不是顺序查找,而是用了跳跃表的结构。Kafka 的每个日志对象中使用了ConcurrentSkipListMap来保存各个日志分段,每个日志分段的baseOffset作为key,这样可以根据指定偏移量来快速定位到消息所在的日志分段。
在这里插入图片描述Kafka 强制要求索引文件大小必须是索引项大小的整数倍,对偏移量索引文件而言,必须为8的整数倍。如果broker端参数log.index.size.max.bytes配置为67,那么Kafka在内部会将其转换为64,即不大于67,并且满足为8的整数倍的条件。

2. 时间戳索引


时间戳索引项的格式
在这里插入图片描述
每个索引项占用12个字节,分为两个部分。

  1. timestamp:当前日志分段最大的时间戳。
  2. relativeOffset:时间戳所对应的消息的相对偏移量。
    时间戳索引文件中包含若干时间戳索引项,每个追加的时间戳索引项中的 timestamp 必须大于之前追加的索引项的 timestamp,否则不予追加。如果 broker 端参数 log.message.timestamp.type设置为LogAppendTime,那么消息的时间戳必定能够保持单调递增;相反,如果是 CreateTime 类型则无法保证。生产者可以使用类似 ProducerRecord(String topic,Integer partition,Long timestamp,K key,V value)的方法来指定时间戳的值。即使生产者客户端采用自动插入的时间戳也无法保证时间戳能够单调递增,如果两个不同时钟的生产者同时往一个分区中插入消息,那么也会造成当前分区的时间戳乱序。
    与偏移量索引文件相似,时间戳索引文件大小必须是索引项大小(12B)的整数倍,如果不满足条件也会进行裁剪。同样假设broker端参数log.index.size.max.bytes配置为67,那么对应于时间戳索引文件,Kafka在内部会将其转换为60。
    每当写入一定量的消息时,就会在偏移量索引文件和时间戳索引文件中分别增加一个偏移量索引项和时间戳索引项。两个文件增加索引项的操作是同时进行的,但并不意味着偏移量索引中的relativeOffset和时间戳索引项中的relativeOffset是同一个值。与上面偏移量索引一节示例中所对应的时间戳索引文件00000000000000000000.timeindex的部分内容如下:
    在这里插入图片描述

总结

提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值