kafka-日志段LogSegment管理操作-源码学习笔记

本文详细介绍了LogSegment的管理方法,包括如何增加、删除日志段,以及查询日志段的细节。探讨了Kafka中日志段的存储策略,并解析了其源码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景

前面一篇文章已经说了日志段和日志的关系,日志通过ConcurrentSkipListMap 保存所有日志段对象,他是线程安全的,健值是可排序的。今天我们来探讨下日志段管理功能。

1、增加日志

Log 类中定义了添加日志段的方法addSegment.


def addSegment(segment: LogSegment): LogSegment = this.segments.put(segment.baseOffset, segment)

通过源码可以看出,实质上就是调用ConcurrentSkipListMap 的put 方法进行操作。

2、删除日志

kafka 本身有很多日志的留存策略,包括基于时间维度的、基于空间维度的和基于Log Start Offset 维度的,根据这些规则确定哪些日志可以删除。
源码中删除的入口是deleteOldSegments():


def deleteOldSegments(): Int = {
    if (config.delete) {
      deleteRetentionMsBreachedSegments() + deleteRetentionSizeBreachedSegments() + deleteLogStartOffsetBreachedSegments()
    } else {
      deleteLogStartOffsetBreachedSegments()
    }
  }

deleteRetentionMsBreachedSegments、deleteRetentionSizeBreachedSegments、deleteLogStartOffsetBreachedSegments三种删除策略分别对应上面三种留存策略。
由于每种留存策略调用的删除方法是deleteOldSegments,我们先来说明下它。


private def deleteOldSegments(predicate: (LogSegment, Option[LogSegment]) => Boolean, reason: String): Int = {
    lock synchronized {
      val deletable = deletableSegments(predicate)
      if (deletable.nonEmpty)
        info(s"Found deletable segments with base offsets [${deletable.map(_.baseOffset).mkString(",")}] due to $reason")
      deleteSegments(deletable)
    }
  }

这段代码主要是两个步骤,1、使用传入的函数计算哪些日志段对象能够被删除。2、调用deleteSegments()方法删除这些日志段。
我们首先来看下deletableSegments:

 private def deletableSegments(predicate: (LogSegment, Option[LogSegment]) => Boolean): Iterable[LogSegment] = {
    if (segments.isEmpty) {
      Seq.empty
    } else {
      val deletable = ArrayBuffer.empty[LogSegment]
      var segmentEntry = segments.firstEntry
      while (segmentEntry != null) {
        val segment = segmentEntry.getValue
        val nextSegmentEntry = segments.higherEntry(segmentEntry.getKey)
        val (nextSegment, upperBoundOffset, isLastSegmentAndEmpty) = if (nextSegmentEntry != null)
          (nextSegmentEntry.getValue, nextSegmentEntry.getValue.baseOffset, false)
        else
          (null, logEndOffset, segment.size == 0)

        if (highWatermark >= upperBoundOffset && predicate(segment, Option(nextSegment)) && !isLastSegmentAndEmpty) {
          deletable += segment
          segmentEntry = nextSegmentEntry
        } else {
          segmentEntry = null
        }
      }
      deletable
    }
  }

1、首先第一步判断,如果没有日志对象直接返回。
2、获取日志段其实位移的第一个Entry日志段,满足下列条件就停止遍历:
(1)predicate 为false。
(2) 扫描到包含Log对象高水位值所在的日志段对象。
(3)最新的日志段对象不包含任何消息。
最新日志段对象是segments 中key 最大的对象,也就是Active segment 是不允许删除的。
满足以上几个条件的就是可以删除的日志段。

我们再来看下 deleteSegments(deletable),这是一个真正删除日志段的方法:

 private def deleteSegments(deletable: Iterable[LogSegment], reason: SegmentDeletionReason): Int = {
    maybeHandleIOException(s"Error while deleting segments for $topicPartition in dir ${dir.getParent}") {
      val numToDelete = deletable.size
      if (numToDelete > 0) {
        // we must always have at least one segment, so if we are going to delete all the segments, create a new one first
        // 不允许删除所有日志段对象。如果一定要做,先创建出一个新的来,然后再把前面的日志段删掉
        if (segments.size == numToDelete)
          roll()
        lock synchronized {
        //确保Log 对象没有被关闭
          checkIfMemoryMappedBufferClosed()
          // remove the segments for lookups
          //删除给定的日志段对象以及对应的物理文件
          removeAndDeleteSegments(deletable, asyncDelete = true, reason)
       //尝试更新日志的Log Start Offset值   
          maybeIncrementLogStartOffset(segments.firstEntry.getValue.baseOffset, SegmentDeletion)
        }
      }
      numToDelete
    }
  }

最后更新Log Start Offset 的原因是,对外可见的消息位移由于删除,可能发生了变化。

3、查询Segment

源码中需要查询日志段对象的地方太多了,但主要都是利用了 ConcurrentSkipListMap 的现成方法。

  • segments.firstEntry:获取第一个日志段对象;
  • segments.lastEntry:获取最后一个日志段对象,即 Active Segment;
  • segments.higherEntry:获取第一个起始位移值≥给定 Key 值的日志段对象;
  • segments.floorEntry:获取最后一个起始位移值≤给定 Key 值的日志段对象。关键位移值管理。
    所以这部分的源码我们只需要了解map 接口提供的接口就行。

今天主要讲解了LogSegment 的一些管理方法,留一个思考题,文章中没有提到更新的操作,你知道是什么原因吗?欢迎留言一起讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值