afka删除数据有两种方式,一种是按照时间,超过一段时间后删除过期消息,第二种是按照消息大小删除数据的,消息数量超过一定大小后删除最旧的数据
但是Kafka的数据是存储在文件系统内的,随机删除数据是不可能的,那么,Kafka是如何删除数据的呢?
Kafka删除数据主逻辑
对应配置: log.cleanup.interval.mins
当前使用值:1
file: core/src/main/scala/kafka/log/LogManager.scala
line: 271
/** * Delete any eligible logs. Return the number of segments deleted. */ def cleanupLogs() { debug("Beginning log cleanup...") var total = 0 val startMs = time.milliseconds for(log <- allLogs) { debug("Garbage collecting '" + log.name + "'") total += cleanupExpiredSegments(log) + cleanupSegmentsToMaintainSize(log) } debug("Log cleanup completed. " + total + " files deleted in " + (time.milliseconds - startMs) / 1000 + " seconds") } |
Kafka 每隔 log.cleanup.interval.mins 分钟调用一次 cleanupLogs ,该函数对所有 Logs 执行清理操作,(目前不确定 Logs 对应的是 Topic 还是 Partition,目测应当是 Partition)
- cleanupExpiredSegments 负责清理超时的数据
- cleanupSegmentsToMaintainSize 负责清理超过大小的数据
清理超时数据 (必选策略)
对应配置:log.retention.hours
当前使用值: 72 (3天)
file: core/src/main/scala/kafka/log/LogManager.scala
line: 237
/** * Runs through the log removing segments older than a certain age */ private def cleanupExpiredSegments(log: Log): Int = { val startMs = time.milliseconds val topic = parseTopicPartitionName(log.name).topic val logCleanupThresholdMs = logRetentionMsMap.get(topic).getOrElse(this.logCleanupDefaultAgeMs) val toBeDeleted = log.markDeletedWhile(startMs - _.messageSet.file.lastModified > logCleanupThresholdMs) val total = log.deleteSegments(toBeDeleted) total } |
- 该函数首先获取 topic,根据 topic 获取日志超时时间,该超时时间可以使用配置 log.retention.hours.per.topic 单独指定,如果没有单独指定,则使用统一的 log.retention.hours 配置。
- 然后扫描所有该日志对应的 Segment 文件,对所有最近修改时间与当前时间差距大于超时时间的日志的 Segment 文件,标记为删除
- 最后删除标记为删除的 Segment 文件
清理超大小数据 (可选策略)
对应配置:log.retention.bytes
当前使用值: -1 (默认值,即不采用该策略)
file: core/src/main/scala/kafka/log/LogManager.scala
line: 250
/** * Runs through the log removing segments until the size of the log * is at least logRetentionSize bytes in size */ private def cleanupSegmentsToMaintainSize(log: Log): Int = { val topic = parseTopicPartitionName(log.dir.getName).topic val maxLogRetentionSize = logRetentionSizeMap.get(topic).getOrElse(config.logRetentionBytes) if(maxLogRetentionSize < 0 || log.size < maxLogRetentionSize) return 0 var diff = log.size - maxLogRetentionSize def shouldDelete(segment: LogSegment) = { if(diff - segment.size >= 0) { diff -= segment.size true } else { false } } val toBeDeleted = log.markDeletedWhile( shouldDelete ) val total = log.deleteSegments(toBeDeleted) total } |
- 当不需要删除或者日志总大小小于配置时,不执行
- 按照从旧到新的顺序扫描各个 Segments,如果该Segment大小 <= 当前日志超过设置值的diff,该Segment会被标记为需要删除
- 执行删除操作
按照 Segment 删除的影响
每个 Segment 文件实际会按照最后一条日志的时间进行删除。当日志中的最后一条日志没有超时时,该文件不会被删除。
对超过大小规则的影响
删除该Segment之后,数据仍然超过大小,才会删除该Segment。如果删除该Segment后,数据大小小于设定上限,则不删除该Segment。
Segment相关配置
log.segment.bytes
- 当前使用值:1073741824
- 注释:单个Segment 的大小设置,达到这个大小时,Kafka会新建一个 Segment 文件
log.roll.hours
- 当前使用值:24
- 隔一段时间,Kafka 会新建一个Segment文件,即便之前的Segment文件没有达到 log.segment.bytes