kafka日志存储(五):LogSegment

为了防止Log文件过大,把log切分成很多个日志文件,每个日志文件对应一个LogSegment,在LogSegment中封装一个FileMessageSet和OffsetIndex对象。
 

class LogSegment(val log: FileMessageSet,//操作对应日志文件FileMessageSet对象
                 val index: OffsetIndex,//操作索引文件的OffsetIndex对象
                 val baseOffset: Long,//第一条消息的offset值
                 val indexIntervalBytes: Int,//索引项之间间隔的最下字节数
                 val rollJitterMs: Long,
                 time: Time) extends Logging {
      //自从上次添加索引项之和,在日志文件中累计加入的Message集合的字节数,判断下次索引项添加的时机。
      private var bytesSinceLastIndexEntry = 0
      //LogSegment的创建时间,当调用truncateTo方法吧整个日志文件清空的时候,此字段会重置为当前时间
      var created = time.milliseconds
}

LogSegment.append()实现了追加消息的功能。第一个参数是第一条消息的offset,如果是压缩的消息,则是内层消息的第一个offset。
 

def append(offset: Long, messages: ByteBufferMessageSet) {
    if (messages.sizeInBytes > 0) {
      trace("Inserting %d bytes at offset %d at position %d".format(messages.sizeInBytes, offset, log.sizeInBytes()))
      //是否满足添加索引项目的条件
      if(bytesSinceLastIndexEntry > indexIntervalBytes) {
        //添加索引
        index.append(offset, log.sizeInBytes())
        this.bytesSinceLastIndexEntry = 0
      }
      // 写日志文件,更新bytesSinceLastIndexEntry
      log.append(messages)
      this.bytesSinceLastIndexEntry += messages.sizeInBytes
    }
}

read方法读取消息,有四个参数

// startOffset指定读取的起始消息的offset
// maxOffset 读取结束的offset,可以为空
// maxSize 指定读取的最大字节数
// maxPosition 指定读取的最大物理地址
  def read(startOffset: Long, maxOffset: Option[Long], maxSize: Int, maxPosition: Long = size): FetchDataInfo = {
    if(maxSize < 0)
      throw new IllegalArgumentException("Invalid max size for log read (%d)".format(maxSize))
    // 日志文件的长度
    val logSize = log.sizeInBytes // this may change, need to save a consistent copy
    // 转换为物理地址,通过offsetIndex.lookup和FileMessageSet.searchFor,查找方法已在之前的博客中介绍
    val startPosition = translateOffset(startOffset)

    // if the start position is already off the end of the log, return null
    if(startPosition == null)
      return null

    val offsetMetadata = new LogOffsetMetadata(startOffset, this.baseOffset, startPosition.position)

    // if the size is zero, still return a log segment but with zero size
    if(maxSize == 0)
      return FetchDataInfo(offsetMetadata, MessageSet.Empty)

    // 计算读取的字节数
    val length = maxOffset match {
      case None =>
        // maxOffset为空,由maxPosition、maxSize决定读取的长度
        min((maxPosition - startPosition.position).toInt, maxSize)
      case Some(offset) =>
        // maxOffset换成物理地址
        if(offset < startOffset)
          return FetchDataInfo(offsetMetadata, MessageSet.Empty)
        val mapping = translateOffset(offset, startPosition.position)
        val endPosition =
          if(mapping == null)
            logSize // the max offset is off the end of the log, use the end of the file
          else
            mapping.position
        min(min(maxPosition, endPosition) - startPosition.position, maxSize).toInt
    }
    //返回消息
    FetchDataInfo(offsetMetadata, log.read(startPosition.position, length))
  }

LogSegment的recover方法根据日志文件重新索引文件。
 

  def recover(maxMessageSize: Int): Int = {
      // 清空索引文件,只是移动position指针,后续的写入会覆盖原有的内容。
    index.truncate()
    //修改索引文件的大小
    index.resize(index.maxIndexSize)
    //记录了已经通过验证的字节数
    var validBytes = 0
    //最后一个索引项对应的物理地址
    var lastIndexEntry = 0
    //FileMessageSet迭代器
    val iter = log.iterator(maxMessageSize)
    try {
      while(iter.hasNext) {
        val entry = iter.next
        entry.message.ensureValid()
        //符合添加索引的条件
        if(validBytes - lastIndexEntry > indexIntervalBytes) {
          // we need to decompress the message, if required, to get the offset of the first uncompressed message
          val startOffset =
            entry.message.compressionCodec match {
              case NoCompressionCodec =>
                entry.offset
              case _ =>
                //对消息进行解压缩,获取第一个消息的offset
                ByteBufferMessageSet.deepIterator(entry).next().offset
          }
          //添加索引项
          index.append(startOffset, validBytes)
          lastIndexEntry = validBytes
        }
        //累加validBytes
        validBytes += MessageSet.entrySize(entry.message)
      }
    } catch {
      case e: CorruptRecordException =>
        logger.warn("Found invalid messages in log segment %s at byte offset %d: %s.".format(log.file.getAbsolutePath, validBytes, e.getMessage))
    }
    //对日志文件进行截断,抛弃后面验证失败的message
    val truncated = log.sizeInBytes - validBytes
    log.truncateTo(validBytes)
    index.trimToValidSize()
    truncated
  }

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值