日志压缩
在很多实践的场景中,key和value的值是不丹变化的,如果消费者只关心key对应的最新value值,可以开启kafka的日志压缩功能,服务端要在后台启动Cleaner线程池,把相同key的消息进行合并。
每个Log都可以通过clean checkpoint部分切分成clean和dirty两部分,clean表示之前已经被压缩过的消息,dirty表示没有被压缩过的消息。
经过压缩之后,clean部分的offset可能是断断续续的,而dirty部门都是连续递增的。
每个Log需要进行日志压缩的迫切程度不同,每个Cleaner只选取最需要被压缩的Log进行处理,迫切程度是通过cleanableRatio决定的。
1. Cleander线程在选定需要清理的Log后,首先为dirty部分建立key和last_offset(这个key出现的最大offset)的对应关系。
2. 重新复制LogSegment,只保留last_offset中记录的消息,抛弃其他消息。
3. 对相邻的LogSegment进行合并,避免出现过小的日志文件和索引文件。
4. value为空的消息会被认为是删除此key对应的消息的标志,此标志消息会被保留一段时间。
LogCleanManager中各个字段如下:
checkpoints:Map[File, OffsetCheckpoint]类型,用来维护data数据目录与cleaner-offset-checkpoint文件之间的对应关系。
inProgress:HashMap[TopicAndPartition, LogCleaningState]: 用于记录正在进行清理的TopicAndPartition的压缩状态。
lock:保护checkpoints集合和inProgress集合锁
pausedCleaningCond:线程阻塞等待压缩状态由LogCleaningAborted转换为LogCleaningPaused
1. 当开始进行压缩的时候,会先进入LogCleanInProgress状态
2. 压缩任务被暂停时进入LogCleaningPaused
3. 压缩任务被中断,则进入LogCleaningPaused状态。
4. 处于LogCleaningPaused状态的topicAndPartition的日志不会被压缩,知道有其他线程恢复它的状态。
def grabFilthiestLog(): Option[LogToClean] = {
inLock(lock) { //加锁
//拿到全部log的cleanercheckpoint
val lastClean = allCleanerCheckpoints()
val dirtyLogs = logs.filter {
//过滤配置为delete的Log
case (topicAndPartition, log) => log.config.compact // skip any logs marked for delete rather than dedupe
}.filterNot {
//过滤掉包含inProgress状态的log
case (topicAndPartition, log) => inProgress.contains(topicAndPartition) // skip any logs already in-progress
}.map {
case (topicAndPartition, log) => // create a LogToClean instance for each
// if the log segments are abnormally truncated and hence the checkpointed offset
// is no longer valid, reset to the log starting offset and log the error event
//获取Log的第一条消息的offset
val logStartOffset = log.logSegments.head.baseOffset
//决定最终压缩开始的位置,firstDirtyOffset的值可能是logStartOffset,也可能是clean checkpoint
val firstDirtyOffset = {
val offset = lastClean.getOrElse(topicAndPartition, logStartOffset)
if (offset < logStartOffset) {
error("Resetting first dirty offset to log start offset %d since the checkpointed offset %d is invalid."
.format(logStartOffset, offset))
logStartOffset
} else {
offset
}
}
为Log创建一个LogToClean对象,在LogToClean对象用维护每个Log的clean部分字节数、dirty部分字节数,以及cleanableRatio
LogToClean(topicAndPartition, log, firstDirtyOffset)
}.filter(ltc => ltc.totalBytes > 0) // skip any empty logs
//获取dirtyLogs集合中cleanableRatio的最大值。
this.dirtiestLogCleanableRatio = if (!dirtyLogs.isEmpty) dirtyLogs.max.cleanableRatio else 0
// 过滤掉cleanableRatio小于配置的minCleanableRatio值的Log
val cleanableLogs = di

最低0.47元/天 解锁文章
1341

被折叠的 条评论
为什么被折叠?



