一、场景分析 之前提到,Kafka中一个分区对应一个Log对象,每个Log对象下面又划分了多个日志段LogSegment。那么这些日志段的划分策略是什么?即满足什么条件时会生成新的日志段,以及生成新日志段的流程是什么样的。这篇来进行详细的分析。
二、图示说明
滚动生成新的日志段流程:
三、源码分析
1. 新的日志段是如何生成的。由于LogSegment是由Log管理的,所以按理说,控制生成新的LogSegment的方法就应该在Log类中。这个方法就是roll()方法,它的注释如下:
* Roll the log over to a new active segment starting with the current logEndOffset.* This will trim the index to the exact size of the number of entries it currently contains.
大概翻译就是: 滚动生成新的active日志段,以当前的LogEndOffset作为日志段的起始偏移量。 这个方法将裁剪索引文件至实际大小。(具体为何裁剪索引文件可以看《深入理解Kafka服务端之索引文件及mmap内存映射 》)
这里要重点关注一个概念: LogEndOffset,简称LEO。是Kafka服务端十分重要的一个概念,后面还会多次提及。 表示的是下一条待写入消息的偏移量。
生成新日志段的流程分析:
def roll(expectedNextOffset: Option[Long] = None): LogSegment = {
maybeHandleIOException(s"Error while rolling log segment for $topicPartition in dir ${dir.getParent}") {
//记录开始时间 val start = time.hiResClockMs() lock synchronized {
//检查索引文件的内存映射是否关闭 checkIfMemoryMappedBufferClosed() //计算新日志段文件的起始偏移量baseOffset val newOffset = math.max(expectedNextOffset.getOrElse(0L), logEndOffset) //生成新的日志文件 val logFile = Log.logFile(dir, newOffset) //如果newOffset对应的日志段文件已存在 if (segments.containsKey(newOffset)) {
//如果active日志段的起始偏移量和上面计算的起始偏移量相同,且active日志段的日志文件大小为0,则删除active日志段 if (activeSegment.baseOffset == newOffset && activeSegment.size == 0) {
warn(s"Trying to roll a new log segment with start offset $newOffset " + s"=max(provided offset = $expectedNextOffset, LEO = $logEndOffset) while it already " + s"exists and is active with size 0. Size of time index: ${activeSegment.timeIndex.entries}," + s" size of offset index: ${activeSegment.offsetIndex.entries}.") //异步删除active日志段(先标记为.delete) deleteSegment(activeSegment) } else {
//如果日志段文件已存在且不是active,则抛异常 throw new KafkaException(s"Trying to roll a new log segment for topic partition $topicPartition with start offset $newOffset" + s" =max(provided offset = $expectedNextOffset, LEO = $logEndOffset) while it already exists. Existing " +