日志存储

文件目录布局

一个分区副本在节点上对应一个log文件夹,同时kafka为了防止文件过大引入了logSegment概念。将log切分成了多个logSegment,相当于将一个大文件切分成多个小文件。logSegment又对应磁盘上多个文件,一般有一个日志文件和两个索引文件,以及可能的其他文件。
关系图
向log中追加消息时是顺序写入,只有最后一个logSegment才能执行昔日如操作,此前的所有logSegment度不能写入数据。
为了便于消息的检索,每个logSegment中的日志文件(以.log结尾)对有对应的两个索引文件:偏移量索引文件(以.index结尾)和时间戳索引文件(以.timeindex结尾)。每个logSegment都有一个基准偏移量baseOffset,用来表示当前logSegment中第一条小消息的offset。偏移量是一个64位的长整型数,日志文件和两个索引文件都是根据基准偏移量命名的,名称固定为20位数字,没有达到的位数则用0填充。
除了以上文件外在一个kafka服务第一次启动的时候,默认的根目录下就会创建一下5个文件:
cleaner-offset-checkpoint,log-start-offset-checkpoint,meta-properties,recovery-point-offset-checkpoint,replication-offset-checkpoint

日志格式的演变

0.8-0.10版本之间为v0版本的日志文件格式
在这里插入图片描述

  • crc32(4B) 校验值。校验范围为magic至value之间
  • magic(1B) 消息格式版本号,此版本的magic值为0
  • attributes(1B) 消息的属性。占1个字节,低3位表示压缩类型:0表示NONE,1表示GZIP,2表示SNAPPY,3表示LZ4,其余保留
  • key length(4B) 表示消息的key的长度。如果为-1则表示没有设置key
  • key 可选
  • value length(4B) 世纪消息体的长度,如果没有则为-1
  • value 消息体
    查看日志可以使用如下脚本
kafka-run-class.sh kafka.tool.DumpLogSegments --file $KAFKA_LOG/topic-create-0/000000000000000.log

0.10-0.11版本之间为v1版本的日志格式
这个版本比v0版本就多了一个timestamp字段,表示消息的时间戳
在这里插入图片描述
这个版本的attributes字段中的低3位和v0版本的一样,还是压缩类型,而第4个位也被利用起来了:1表示timestamp类型为createTime,而1表示timestamp类型为logAppendTime,其余保留。时间戳类型可由broker端参数log.message.timestamp.type来配置,默认为CreateTime,即采用生产者创建消息是的时间戳。
如果在创建ProducerRecord是没有显式指定消息的时间戳,那么KafakProducer也会在发送这条消息前自动添加上。下面是KafkaProducer中与此对应的一句关键代码

	long timestamp = record.timestamp() == null ? time.milliseconds() : record.timestamp();

0.11版本后使用的是v2版本的日志
v2版本二中消息集称为Record Batch,而不是先前的Message Set。
在这里插入图片描述
v2版本的消息格式去掉了crc32,另外增加了legnth,timestamp delta,offset delta和headers,并且attributes字段被弃用了

  • length 消息长度
  • attributes 弃用,但还是在消息格式中占据1B
  • timestamp delta 时间戳增加
  • offset delta 位移增量,保存与RecordBatch其实位移的差值,可以节省占用的字节数
  • headers 该这段主要用于支持应用级别的扩展。
    v1版本消息,如果用户指定的timestamp类型是logAppendtime而不是createTime,那么消息从生产者进入broker后,timestamp字段会被更新,此时消息的crc值江北重新计算,而此值在生产者中已经被计算过一次。再者,broker端在进行消息格式转换时(比如v1版本转成v0版的消息格式)也会重新计算crc值,所以v2将crc的字段从Record移到了RecordBatch中

消息压缩

Kafka日志中使用哪种压缩方式是通过参数compression.type来配置的,默认值为"producer",表示保留生产者使用的压缩方式。这个参数还可以配置为gzip,snappy和lz4,如果参数compression.type为uncompressed则表示不压缩,这个是在broker端设置的,和生产者端的compression.type不一样。
当消息压缩是是将整个消息集进行压缩作为内层消息,内层消息整体作为外层的value
在这里插入图片描述
压缩的外层消息中的key为null,value字段中保存的是多条压缩消息,其中Record表示的是从crc32到value的消息格式。当生产者创建压缩消息的时候,对内层压缩消息设置的offset从0开始为每个内层消息分配offset,可以理解为是这个消息集中的相对offset。而外层offset的值为内层消息中最后一条消息的绝对offset。
计算公式
RO = IO_of_a_message - IO_of_the_last_message
AO = AO_of_last_inner_message + RO

日志索引

日志索引

kafka的索引文件以稀疏索引的方式构建消息的索引,每当写入一定量(有broker端参数log.index.interval.bytes指定,默认值为4096)时,偏移量索引文件和时间戳索引文件分别增加一个偏移量索引项和时间戳索引项。
稀疏索引通过MappedByteBuffer将索引文件映射到内存中,以加快索引的查询速度。查询指定偏移量时,使用二分查找法来快速定位偏移量的位置,如果指定的偏移量不在索引文件中,则会返回小于指定偏移量的最大偏移量。时间索引同理,当时间索引中记录的是时间和偏移量的对应值,而偏移量索引记录的是偏移量和磁盘物理值的对应关系。
日志分段文件切分包含以下几个条件,满足一个就可以了

  • 当前日志分段文件的大小超过broker端参数log.segment.bytes配置的值,默认1GB
  • 当前日志分段中消息的最大时间戳与当前系统的时间戳的差值大于log.roll.ms或log.roll.hours参数配置的值。ms更优先,可以理解为在一定时间内没有消息写入后就另起一个segment日志分段
  • 偏移量索引文件或时间戳索引文件的大小达到broker端参数log.index.size.max.bytes的配置,默认10MB
  • 追加的消息的偏移量与当前日志分段的基准偏移量之间差值大于Integer.MAX_VALUE
    kafka在创建索引文件的时候会为其预分配log.index.size.max.bytes大小的空间,只用当索引文件切分的事就,kafka才会把索引文件裁剪到实际的数据大小

偏移量索引

消息的偏移量占用8个字节,由relativeOffset和position各4B来占用,除了文件名记录了该segment的第一条消息的偏移量后,索引文件中的都是相对偏移量。所以当相对偏移量超过了Integer.MAX_VALUE时将不再是4B了。

时间戳索引

时间戳偏移量占用了12B,时间戳占8个字节,相对索引占4B。
查询流程

  • 将目标时间戳和每个日志分段中的最大时间戳逐一对比,知道找打不小于目标时间戳的最大时间戳所对应的日志分段。
  • 找到相应的日志分段之后,在时间戳索引文件中使用二分查找来查找到不大于目标时间戳的最大时间戳对应的日志的时间戳索引项,然后去找其对应的偏移量索引文件该日志的物理地址
  • 在偏移量索引文件中使用二分查找法来找
  • 确定日志分段的目标位置

日志清理

由broker端参数log.cleanup.policy确定,有两个值delete和compact,默认delete
主题级别的是cleanup.policy

日志删除

在kafka日志管理中会有一个专门的日志删除任务来周期性地检查和删除不符合保留条件的日志分段文件,这个周期由broker端参数log.retention.check.interval.ms来配置,默认300000.
日志删除有3中保留策略:时间,日志大小和日志其实偏移量

  • 基于时间
    由broker端参数log.retention.hours、log.retention.minutes和log.retention.ms。起哄ms等级最高,现在默认是用hours且默认值为168,即7天。
    删除过程,先找每个日志分段的最大时间戳(一般默认为找时间戳索引文件的最后一条),如果该日志分段是activeSegment或不是全部日志都过期就先切分日志在执行删除。
    删除日志分段时,先从Log对象中所维护日志分段的跳跃表中移除待删除的日志分段,以保证没有线程能操作该日志分段,然后将该日志分段对应所有文件添加一个".delete"的后缀,最后交由一个一"delete-file"命名的延迟任务来删除。
  • 基于日志大小
    当日志的大小超过了设定的阈值(broker端参数log.retention.bytes),将超出的删除掉。这种可能会导致频繁删除。删除过程同上
  • 基于偏移量
    先确定一个logStartOffset,如果该日志分段的下一个日志分段的基准日志offset小于这个就删除该日志分段。

日志压缩

该做法是把每个key里面的最新的value保留,而其余的删除的意思。
在cleaner-offset-checkpoint文件中将整个日志分为clean清理过的和dirty未清理过的。
在这里插入图片描述
firstDirtyOffset表示dirty部分的起始偏移量,而firstUncleanableOffset为dirty的截止偏移量,activeSegment不会参与日志压缩过程,broker端参数log.cleaner.min.compaction.lag.ms(默认为0)来配置消息在被清理前的最小保留时间,默认情况下firstUncleanableOffset等于activeSegment的baseOffset。
dirtyRatio = dirtyBytes / (cleanBytes + dirtyBytes) 这个是最小污浊率,为了防止日志补益药的频繁清理操作,kafka还是使用了参数log.cleaner.min.cleanable.ratio来限定可以进行清理操作的最小污浊率。kafka中的__consumer_offsets使用的就是日志压缩策略。
kafka中的每个日志清理线程会使用一个名为SkimpyOffsetMap的对象来构建key和offset的映射关系的哈希表。日志清理需要遍历两次日志文件,第一次遍历吧每个可以的哈细致和最后出现的offset都保留在SkimpyOffsetMap中。第二次遍历会检查每个消息是否符合保留条件。
默认情况下,SkimpyOffsetMap使用MD5来计算key的哈稀值,占用16B空间,为了防止哈希冲突国语频繁,也可以通过broker参数log.cleaner.io.buffer.load.factor(0.9)来调整负载因子。偏移量占用8B空间,股一个映射项占用大小24B。而SkimpyOffsetMap的大小是由log.cleaner.dedupe.buffer.size参数来确定(128M),当然是所有删除线程的总和。SkimpyOffsetMap记录key的个数为128M * 0.9 / 24B = 5033164个key。
在这里插入图片描述
假设该日志没有进行过日志压缩,checkpoint中的记录为0,则将activeSegment前的日志分段经行一次压缩,这样每个非活跃的日志分段的大小都有所缩减,checkpoint的值也有所变化。执行第二次压缩是会合并一些较小的日志分段。压缩过程中将每一个需要保留的消息复制到一个以".clean"为后缀的临时文件中,此临时文件以当前日志分组中第一个日志分度的文件命名,压缩后将".clean"文件修改成".swap"文件,在删除了原来的日志文件后,在去掉后缀。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值