Log 的常见操作分为 4 大部分。
1. 高水位管理操作:高水位的概念在 Kafka 中举足轻重,对它的管理,是 Log 最重要的功能之一。
2. 日志段管理:Log 是日志段的容器。高效组织与管理其下辖的所有日志段对象,是源码要解决的核心问题。
3. 关键位移值管理:日志定义了很多重要的位移值,比如 Log Start Offset 和 LEO 等。确保这些位移值的正确性,是构建消息引擎一致性的基础。
4. 读写操作:所谓的操作日志,大体上就是指读写日志。

LogOffsetMetadata对象

@volatile private var highWatermarkMetadata: LogOffsetMetadata = LogOffsetMetadata(logStartOffset)

高水位的起始值是Log Start Offset的值,每个 Log 对象都会维护一个 Log Start Offset 值,当首次构建高水位时,它会被赋值成 Log Start Offset 值。先看看LogOffsetMetadata是什么对象:

case class LogOffsetMetadata(messageOffset: Long,
                             segmentBaseOffset: Long = Log.UnknownOffset, relativePositionInSegment: Int = LogOffsetMetadata.UnknownFilePosition)

messageOffset:消息位移值。

segmentBaseOffset:保存该位移值所在日志段的起始位移。日志段起始位移值辅助计算两条消息在物理磁盘文件中位置的差值,即两条消息彼此隔了多少字节。这个计算有个前提条件,即两条消息必须处在同一个日志段对象上,不能跨日志段对象。否则它们就位于不同的物理文件上,计算这个值就没有意义了。这里的 segmentBaseOffset,就是用来判断两条消息是否处于同一个日志段的。

relativePositionSegment:保存该位移值所在日志段的物理磁盘位置。这个字段在计算两个位移值之间的物理磁盘位置差值时非常有用。在读取日志的时候需要计算位置之间的字节数,假设每次读取时只能读 1MB 的数据,那么,源码肯定需要关心两个位移之间所有消息的总字节数是否超过了 1MB。

LogOffsetMetadata的定义如下:

case class LogOffsetMetadata(messageOffset: Long,
                             segmentBaseOffset: Long = Log.UnknownOffset,
                             relativePositionInSegment: Int = LogOffsetMetadata.UnknownFilePosition) {

  // check if this offset is already on an older segment compared with the given offset
  def onOlderSegment(that: LogOffsetMetadata): Boolean = {
    if (messageOffsetOnly)
      throw new KafkaException(s"$this cannot compare its segment info with $that since it only has message offset info")

    this.segmentBaseOffset < that.segmentBaseOffset
  }

  // check if this offset is on the same segment with the given offset
  // 这个方法就是用来判断给定的两个 LogOffsetMetadata 对象是否处于同一个日志段的。
  // 判断方法很简单,就是比较两个 LogOffsetMetadata 对象的 segmentBaseOffset 值是否相等。
  def onSameSegment(that: LogOffsetMetadata): Boolean = {
    if (messageOffsetOnly)
      throw new KafkaException(s"$this cannot compare its segment info with $that since it only has message offset info")

    this.segmentBaseOffset == that.segmentBaseOffset
  }

  // compute the number of messages between this offset to the given offset
  def offsetDiff(that: LogOffsetMetadata): Long = {
    this.messageOffset - that.messageOffset
  }

  // compute the number of bytes between this offset to the given offset
  // if they are on the same segment and this offset precedes the given offset
  // 计算两个offset的物理位置差值
  def positionDiff(that: LogOffsetMetadata): Int = {
    if(!onSameSegment(that))
      throw new KafkaException(s"$this cannot compare its segment position with $that since they are not on the same segment")
    if(messageOffsetOnly)
      throw new KafkaException(s"$this cannot compare its segment position with $that since it only has message offset info")

    this.relativePositionInSegment - that.relativePositionInSegment
  }

  // decide if the offset metadata only contains message offset info
  def messageOffsetOnly: Boolean = {
    segmentBaseOffset == Log.UnknownOffset && relativePositionInSegment == LogOffsetMetadata.UnknownFilePosition
  }

  override def toString = s"(offset=$messageOffset segment=[$segmentBaseOffset:$relativePositionInSegment])"
}

高水位管理操作

A. 获取和设置高水位值

// getter method:读取高水位的位移值
def highWatermark: Long = highWatermarkMetadata.messageOffset

  private def updateHighWatermarkMetadata(newHighWatermark: LogOffsetMetadata): Unit = {
    if (newHighWatermark.messageOffset < 0)
      throw new IllegalArgumentException("High watermark offset should be non-negative")

    lock synchronized { // 保护Log对象修改的Monitor锁
      // 赋值新的高水位值
      highWatermarkMetadata = newHighWatermark
      // 处理事务状态管理器的高水位值更新逻辑
      producerStateManager.onHighWatermarkUpdated(newHighWatermark.messageOffset)
      // First Unstable Offset是Kafka事务机制的一部分
      maybeIncrementFirstUnstableOffset()
    }
    trace(s"Setting high watermark $newHighWatermark")
  }

B. 更新高水位值
        源码定义了两个更新高水位值的方法:updateHighWatermark 和 maybeIncrementHighWatermark。从名字上来看,前者是一定 要更新高水位值的,而后者是可能会更新也可能不会。

def updateHighWatermark(hw: Long): Long = {
    // 新高水位值一定介于[Log Start Offset,Log End Offset]之间
    val newHighWatermark = if (hw < logStartOffset)
      logStartOffset
    else if (hw > logEndOffset)
      logEndOffset
    else
      hw
    // 调用Setter方法来更新高水位值
    updateHighWatermarkMetadata(LogOffsetMetadata(newHighWatermark))
    newHighWatermark
  }
def maybeIncrementHighWatermark(newHighWatermark: LogOffsetMetadata, isClusterBackup:Boolean = false): Option[LogOffsetMetadata] = {
    // 新高水位值不能越过Log End Offset
    if (newHighWatermark.messageOffset > logEndOffset)
      throw new IllegalArgumentException(s"High watermark $newHighWatermark update exceeds current " +
        s"log end offset $logEndOffsetMetadata")

    lock.synchronized {
      // 获取老的高水位值
      // fetchHighWatermarkMetadata如果没有就直接从log里面存的hw取到offsetMetadata
      val oldHighWatermark = fetchHighWatermarkMetadata

      // Ensure that the high watermark increases monotonically. We also update the high watermark when the new
      // offset metadata is on a newer segment, which occurs whenever the log is rolled to a new segment.
      // 新高水位值要比老高水位值大以维持单调增加特性,否则就不做更新!
      // 另外,如果新高水位值在新日志段上,也可执行更新高水位操作
      if (oldHighWatermark.messageOffset < newHighWatermark.messageOffset ||
        (oldHighWatermark.messageOffset == newHighWatermark.messageOffset && oldHighWatermark.onOlderSegment(newHighWatermark) ||
          (isClusterBackup && oldHighWatermark.messageOffset > newHighWatermark.messageOffset && newHighWatermark.messageOffset >= 0))) {
        updateHighWatermarkMetadata(newHighWatermark)
        // 返回老的高水位值
        Some(oldHighWatermark)
      } else {
        None
      }
    }
  }

        updateHighWatermark 方法,主要用在 Follower 副本从 Leader 副本获取到消息后更新高水位值。一旦拿到新的消息,就必须要更新高水位值;而 maybeIncrementHighWatermark 方法,主要是用来更新 Leader 副本的高水位值。需要注意的是,Leader 副本高水位值的更新是有条件的——某些情况下会更新高水位值,某些情况下可能不会。

        Follower 副本成功拉取 Leader 副本的消息后必须更新高水位值,但 Producer 端向 Leader 副本写入消息时,分区的高水位值就可能不需要更新——因为它可能需要等待其他 Follower 副本同步的进度。

C. 读取高水位值

        关于高水位值管理的最后一个操作是 fetchHighWatermarkMetadata 方法。它不仅仅是获取高水位值,还要获取高水位的其他元数据信息,即日志段起始位移和物理位置信息。下面是它的实现逻辑:

private def fetchHighWatermarkMetadata: LogOffsetMetadata = {
    checkIfMemoryMappedBufferClosed() // 读取时确保日志不能被关闭

    val offsetMetadata = highWatermarkMetadata // 保存当前高水位值到本地变量,避免多线程访问干扰
    if (offsetMetadata.messageOffsetOnly) { //没有获得到完整的高水位元数据
      lock.synchronized {
        // 通过读日志文件的方式把完整的高水位元数据信息拉出来
        val fullOffset = convertToOffsetMetadataOrThrow(highWatermark)
        // 然后再更新一下高水位对象
        updateHighWatermarkMetadata(fullOffset)
        fullOffset
      }
    } else {// 否则,直接返回即可
      offsetMetadata
    }
  }