Kafka 的日志清理策略有哪些?
Kafka 的日志清理(Log Cleanup)是管理磁盘空间、确保系统稳定运行的核心机制。因为 Kafka 会将消息持久化到磁盘上,如果不进行清理,磁盘空间最终会被耗尽。
Kafka 提供了两种主要的日志清理策略,由配置参数 cleanup.policy 来控制:Delete(删除策略) 和 Compact(压缩/压实策略)。此外,还可以将两者混合使用。
需要特别注意的是:所有的清理策略都只针对“非活跃的日志分段(Inactive Log Segments)”生效,当前正在写入的活跃分段(Active Segment)是绝对不会被清理的。
以下是详细的策略解析:
1. 删除策略(Delete Policy)
配置项:cleanup.policy = delete(这是 Kafka 的默认策略)
核心思想:直接删除旧的日志分段(Segment)。Kafka 允许根据时间或文件大小来决定何时删除数据。
A. 基于时间的保留(Time-based Retention)
Kafka 会定期检查日志分段的最后修改时间,如果超过了设定的阈值,就会将其删除。
- 配置参数(优先级从高到低:ms > minutes > hours):
log.retention.ms:毫秒级别。log.retention.minutes:分钟级别。log.retention.hours:小时级别(默认值为 168 小时,即 7天)。
- 适用场景:绝大多数普通的事件流、日志收集系统。例如:“我们只需要保留最近一周的用户行为数据”。
B. 基于大小的保留(Size-based Retention)
Kafka 会检查每个 Partition(分区) 的总日志大小,如果超过了设定的阈值,就会从最旧的日志分段开始删除,直到分区大小降到阈值以下。
- 配置参数:
log.retention.bytes:限制单个 Partition 的最大字节数。默认值为-1(即不限制大小)。
- 注意:这个大小限制是作用于单个分区的,而不是整个 Topic 或整个 Broker 集群。
C. 触发机制
Kafka 会启动一个后台定时任务来检查是否需要删除日志。
- 配置参数:
log.retention.check.interval.ms(默认 5 分钟检查一次)。
2. 压缩/压实策略(Compact Policy)
配置项:cleanup.policy = compact
核心思想:不直接丢弃旧数据,而是针对每个 Key 保留最新的一条 Value。如果一条消息有相同的 Key,较旧的 Key-Value 将被清理掉,就像 HashMap 一样覆盖旧值。
前提条件:消息必须要有 Key(key != null)。
A. 工作原理
- 日志被分为两部分:已清理部分(Clean) 和 未清理部分(Dirty)。
- 后台的 Log Cleaner 线程会扫描未清理部分,将 Key 及其最新的 Offset 放入一个内存哈希表中。
- 接着,Cleaner 线程会重新拷贝日志分段,在拷贝过程中,如果发现某条消息的 Offset 比内存哈希表中的 Offset 小(说明有更新的值),就会将该旧消息丢弃。
- 最终生成一个物理上更小的新日志分段。
B. 如何彻底删除一个 Key?(墓碑消息 Tombstone)
既然 Compact 策略是保留最新的值,那如果我想彻底删除某个 Key 怎么办?
- 做法:向 Kafka 发送一条该 Key 的消息,并将 Value 设置为
null(这种消息被称为 墓碑消息 Tombstone)。 - 机制:Log Cleaner 发现墓碑消息后,会将其作为该 Key 的最新状态保留一段时间(由
log.cleaner.delete.retention.ms控制,默认 24 小时),确保消费者有足够的时间读取到这个“删除事件”。超时后,墓碑消息本身也会被物理删除,这个 Key 就彻底消失了。
C. 触发条件
并不是只要有相同 Key 就立刻压缩,只有当“脏数据”(未压缩数据)达到一定比例时才会触发。
- 配置参数:
min.cleanable.dirty.ratio(默认 0.5,即脏数据占总数据 50% 时触发压缩)。
D. 适用场景
- 保存系统最新状态:例如保存用户的最新余额、商品的最新库存。
- CDC(变更数据捕获):同步数据库的变更日志(Debezium 等工具常用)。
- Kafka 内部的 Offset 存储:著名的内部 Topic
__consumer_offsets默认就是使用的 compact 策略。
3. 混合策略(Delete + Compact)
配置项:cleanup.policy = delete,compact
核心思想:结合上述两者的特点。
- 一方面,日志 Cleaner 线程会持续对具有相同 Key 的数据进行压缩,保留最新状态。
- 另一方面,如果某个日志分段整体满足了基于时间或基于大小的删除条件(例如超过了 7 天),该日志分段还是会被整体物理删除。
适用场景:
既想维持数据的最新状态,又不想永久保存僵尸数据。例如:“我想保留每个用户最近的登录位置(Compact),但我只关心最近 30 天内活跃过的用户(Delete)”。
总结与对比
| 策略 | 机制 | 适用场景 | 消息是否需要 Key | 磁盘空间控制 |
|---|---|---|---|---|
| Delete (删除) | 按时间或大小整体抛弃旧分段 | 普通消息、行为日志、访问日志等流水型数据 | 不需要 | 精确,可控性极高 |
| Compact (压缩) | 相同 Key 只保留最新的 Value | 状态表、缓存刷新、数据库 CDC 数据同步 | 必须有 | 取决于 Key 的数量(Key 越多,占用越大) |
| 混合 | 既做状态压缩,也做定时删除 | 有时间窗口要求的状态同步 | 必须有 | 精确可控 |
配置级别:
清理策略既可以在 Broker 级别(全局默认)通过 server.properties 配置,也可以在 Topic 级别 通过 Kafka 命令动态修改覆盖(推荐做法)。