LogSegment分析

为了防止Log文件过大,将Log切分成多个日志文件来管理,每一个日志文件对应着一个LogSegment。在LogSegment封装了TimeIndex

,OffsetIndex以及FileMessageSet对象,提供日志文件和索引文件的读写功能和其他功能

 

一 LogSegment的核心字段

log: 用于操作对应消息日志文件的FileMessageSet对象

index: 用于操作对应offset 索引文件的 OffsetIndex对象

timeIndex: 用于操作对应时间索引文件的TimeIndex对象

baseOffset: 每一个日志文件的第一个消息的offset

indexIntervalBytes: 索引项之间间隔的最小字节数,也就是隔多少字节写一次索引

bytesSinceLastIndexEntry: 自上次添加index entry后在日志文件中累计加入的message set的字节数,用于判断下一次索引添加的时机

rollingBasedTimestamp: 用于基于时间的日志滚动的时间戳

maxTimestampSoFar: 目前为止最大的时间,也就是timeIndex文件最后一个entry的时间戳

offsetOfMaxTimestamp:timeIndex文件最后一个entry的对应的offset

 

二 重要的方法

2.1 append 根据指定的offset开始添加消息,如果满足添加索引的条件,也会添加offset 和 time 索引。注意这个方法不是线程安全的,在多线程环境下需要保证线程安全

@nonthreadsafe

def append(firstOffset: Long, largestTimestamp: Long, offsetOfLargestTimestamp: Long, messages: ByteBufferMessageSet) {
  if (messages.sizeInBytes > 0) {
    trace("Inserting %d bytes at offset %d at position %d with largesttimestamp %d at offset %d"
       
.format(messages.sizeInBytes, firstOffset, log.sizeInBytes(), largestTimestamp, offsetOfLargestTimestamp))
    // 获取log现在的物理位置
   
val physicalPosition = log.sizeInBytes()
    if (physicalPosition == 0)
      rollingBasedTimestamp= Some(largestTimestamp)
    // 调用Log#append添加消息,还是先写入内存的,
   
log.append(messages)
    // 更细内存中最大的时间戳和相对应的offset
   
if (largestTimestamp > maxTimestampSoFar) {
      maxTimestampSoFar= largestTimestamp
     
offsetOfMaxTimestamp= offsetOfLargestTimestamp
   
}
    // 自从上一次添加索引到现在累计的字节数如果大于添加索引的间隔字节数,则添加索引
   
if(bytesSinceLastIndexEntry > indexIntervalBytes) {
      index.append(firstOffset, physicalPosition)
      timeIndex.maybeAppend(maxTimestampSoFar, offsetOfMaxTimestamp)
      // 添加完毕后,把bytesSinceLastIndexEntry置为0,重新累计消息字节数
     
bytesSinceLastIndexEntry
= 0
   
}
    bytesSinceLastIndexEntry+= messages.sizeInBytes
 
}
}

 

2.2 read 从log segment第一个offset开始,读取消息,第一个offset应该大于指定的startOffset

startOffset: 指定读取的起始消息的offset

maxOffset: 指定读取结束的offset,可以为空

maxSize: 指定读取最大的字节数

maxPosition: 指定读取最大的物理地址,可选,默认是日志文件大小

@threadsafe
def read(startOffset: Long, maxOffset: Option[Long], maxSize: Int, maxPosition: Long = size,
         minOneMessage: Boolean = false): 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
  // 找到第一个要读取的消息的物理文件位置,startOffset准换成物理地址
  val startOffsetAndSize = translateOffset(startOffset)

  // 如果起始位置已经在日志的末尾返回null
  if (startOffsetAndSize == null)
    return null

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

  // 调整maxsize大小,如果消息太大,大于maxSize,必须从maxSizemessageSetSize取出最小的
  val adjustedMaxSize =
    if (minOneMessage) math.max(maxSize, messageSetSize)
    else maxSize

  if (adjustedMaxSize == 0)
    return FetchDataInfo(offsetMetadata, MessageSet.Empty)

  // 计算消息长度,判断是否从给定的maxOffset读取
  val length = maxOffset match {
    case None =>
      min((maxPosition - startPosition.position).toInt, adjustedMaxSize)
    case Some(offset) =>
      if (offset < startOffset)
        return FetchDataInfo(offsetMetadata, MessageSet.Empty, firstMessageSetIncomplete = false)
      // offset转化为实际物理地址,如果没有则把当前日志大小更新为endPosition
      val mapping = translateOffset(offset, startPosition.position)
      val endPosition =
        if (mapping == null)
          logSize
        else
          mapping._1.position
      min(min(maxPosition, endPosition) - startPosition.position, adjustedMaxSize).toInt
  }
  // 构造FetchDataInfo对象
  FetchDataInfo(offsetMetadata, log.read(startPosition.position, length),
    firstMessageSetIncomplete = adjustedMaxSize < messageSetSize)
}

 

2.3 translateOffset 将逻辑offset转换成物理的地址

在读取日志文件之前,需要将startOffset和maxOffset转换成对应的物理地址才能用,举个例子:

假设startOffset = 2021,那么我们来分析整个过程:

# 我们将绝对的offset 2021 转换成offset index文件中使用的相对offset,假设baseOffset=2000, 2021 – 2000 = 21

# 在offset 索引文件中查找21属于哪一个区间的,假设索引文件片段如下:

那么21属于20-30区间,那么就从position=478开始找绝对offset为2021的消息

# 通过FileMessageSet.searchFor遍历查找FileMessageSet得到(30,567)这个位置信息。

为什么是30呢?因为21这消息与其他消息被压缩后在一起你构成了offset=2021这个外层消息,并存入日志文件

 

private[log] def translateOffset(offset: Long, startingFilePosition: Int = 0): (OffsetPosition, Int) = {
  val mapping = index.lookup(offset)
  log.searchForOffsetWithSize(offset, max(mapping.position, startingFilePosition))
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

莫言静好、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值