【Kafka 简明原理】你从来没听说过的日志紧缩

2,197 阅读5分钟

文章首发于公众号【大数据学徒】,感兴趣请搜索 dashujuxuetu 或者文末扫码关注。

一般来说 Kafka 采用过期策略来清理日志,segment 文件达到一定时间或大小就会被删除,这样保证能够及时为新消息腾出空间。但事实上除了删除过期日志之外,Kafka 还有另外一种清理日志的方法,叫做日志紧缩(Log Compaction),本文介绍日志紧缩的相关内容。


思维导图:

1. 紧缩定义

考虑以下两个场景:

  1. 应用使用 Kafka 来存储用户的邮箱或其它信息,在这种情况下,如果用户修改了自己的邮箱,那么只需要保存最新的邮箱即可,旧的邮箱已经毫无用处了,但是如果采用过期策略,那么旧的邮箱也会保存很久直到过期。
  2. 应用使用 Kafka 来存储自己的当前状态,每当自己的状态发生改变时,就向 Kafka 写入新的状态信息,如果应用出现故障,它可以从 Kafka 中读取最后一次的状态来进行恢复,在这种情况下,只有最新一次写入的状态有用,只需要存储它即可,之前写入的旧状态可以删除。

日志紧缩可以很好的应用在上面两种场景中,它的定义是:针对有 key 的 topic,对于每个 key,只存储最近一次的 value,既满足应用的需求,又加快了日志清理的速度。没有指定 key 的 topic 无法进行紧缩。

重点:日志的过期删除是针对 segment 的,而日志紧缩是针对 topic 进行的,又因为不同 key 的消息存储在不同的分区上,所以日志紧缩也是针对分区进行的,且在紧缩之前,消费者可以消费所有未被紧缩的日志

broker 和 topic 都有日志紧缩相关的配置,broker 的配置项为 log.cleanup.poliy,topic 的配置项为 cleanup.policy,这两个配置的值都有三个选择,deletecompact 或者 delete,compact,并且 topic 的配置会覆盖 broker 的配置。但是一般情况下,不需要设置 delete,compact,因为这样既紧缩又删除,会产生意想不到的后果。

2. 紧缩概述

启用日志紧缩功能的方法是:将 log.cleaner.enabled 设置为 true

对于日志紧缩来说,日志可以被看做两部分:干净的部分脏的部分,干净的部分指的是已经被紧缩过的日志,在这个部分里,每个 key 只存储最后写入的 value;脏的部分是紧缩之后又写入的数据

每个 broker 有一个 compaction manager 的线程和几个 compaction 线程,它们每次都选择脏数据比例最高的分区来进行日志紧缩。

3. 构建辅助 map

对于每个需要被紧缩的分区,紧缩线程读取脏数据在内存中创建一个辅助的 map,每个 map 项的 key 是消息 key 的哈希(16 字节),value 是最近一次出现这个 key 的消息的 offset(8 字节),每个 map 项占用 24 字节,因为大部分情况下,key 会重复出现,所以辅助 map 的大小不会很大。

每个 broker 可以用来构建辅助 map 的内存大小是可配置的,参数为 log.cleaner.dedupe.buffer.size,默认值是 128M,紧缩线程的数量也是可配置的,参数为 log.cleaner.threads,默认值为 1,各个线程共用这些内存,这个内存至少要能存储一个 segment 构建出的辅助 map,如果不能则报错,管理员需要调大参数。此外,线程之间可能会互相等待空间的释放来进行下一次紧缩。

4. 实际紧缩过程

构建完辅助 map 后,紧缩线程读取干净的 segment文件,并和辅助 map 做比对,然后生成一个新的 segment 文件,并将原 segment 文件替换。

如果消息 key 的哈希值在辅助 map 中找不到,说明这条消息仍然是最新的,将这条消息拷贝到新的 segment 中,否则说明这个 key 已经有更新的 value,忽略这条消息。读完干净的 segment 文件之后,紧缩线程将辅助 map 中的各个 value (8 字节 offset)对应的消息也拷贝到新的 segment 文件中,然后替换原来的 segment 文件。

注意:紧缩过后的消息仍然是有序的,消息的 offset 也不会改变。

看个来自 官网的例子,紧缩前后的变化:

5. 如何删除某个 key

如果某个用户注销了自己的账号,那么有必要将这个用户的所有信息都删除,日志紧缩需要考虑如何删除某个 key,即对于这个 key,最新的那条消息也不存储了。怎么做呢?

方法是:应用发送一条包含需要被删除的 key,但是 value 为空的消息,当紧缩线程发现了这个消息,它先按照正常的紧缩做,然后将这条特殊的消息(叫做“墓碑”消息)保存一段时间,在这段时间里,消费者可以读到这条消息然后知道这个 key 将要被删除,消费者可以进行相应的准备。当保存时间到了之后,墓碑消息也会被删除,这个 key 再也不存在。保存时间的配置参数为 log.cleaner.delete.retention.msdelete.retention.ms,默认值是 24 小时。

6. 何时紧缩?

  1. 和过期策略中,永远不会删除处于活跃状态的 segment 一样,紧缩线程也不会紧缩正在写入的 segment。
  2. 有两个参数控制消息被生产后多长时间可以被紧缩,分别是 log.cleaner.min.compaction.lag.mslog.cleaner.max.compaction.lag.ms,分别是一个消息从生产后到被紧缩的时间间隔的最小值和最大值,其它相关的参数还有 min.cleanable.dirty.ratio,不在此赘述。

欢迎交流讨论,吐槽建议,分享收藏。

勤学似春起之苗,不见其增,日有所长 辍学如磨刀之石,不见其损,日有所亏 关注【大数据学徒】,用技术干货助你日有所长

大数据学徒