OffsetIndex和TimeIndex分析

为了提高查找消息的性能,为每一个日志文件添加2个索引索引文件:OffsetIndex 和 TimeIndex,她俩分别对应着磁盘上两个索引文件,与FileMessageSet共同构成一个LogSegment对象。

OffsetIndex索引文件的格式: 每一个索引项为8字节,其中相对offset占用4字节,消息的物理地址(position)占用4个字节

 

 

这样就实现了相对offset与物理地址的映射,相对offset表示消息相对于baseOffSet的偏移量,例如分段后的一个日志文件的baseOffset是32450,它的文件名就是32450.log,那么offset为32455的消息在相对offset就是32455-32450 = 5。

另外OffsetIndex是稀疏索引,也就是说不会存储所有的消息的相对offset和position

OffsetIndex和TimeIndex分析_第1张图片

OffsetIndexhe核心字段:

file: 指向磁盘上的索引文件

baseOffset: 对应日志文件第一个消息的offset
mmap: 用来操作索引文件的MappedByteBuffer

lock: ReentrantLock对象,在mmap进行操作的时候需要加锁保护

_entries:当前索引文件索引项的个数

_maxEntries: 当前索引文件中最多能够保存索引项个数

_lastOffset: 保存最后一个索引项的offset

/**
 *
因为会有多个handler线程并发写入索引文件,所以这些字段使用@volatile,保证线程之间的可见性
 */
@volatile
protected var mmap: MappedByteBuffer= {
  // 如果索引文件不存在则创建返回true,否则存在返回false
 
val newlyCreated = _file.createNewFile()
  val raf = new RandomAccessFile(_file, "rw")
  try {
    /* 如有必要进行预分配空间 */
   
if(newlyCreated) {
      //maxIndexSize值大小对索引文件分配大小,分配结果是小于maxIndexSize *8
     
if(maxIndexSize < entrySize)
        throw new IllegalArgumentException("Invalidmax index size: " + maxIndexSize)
      raf.setLength(roundDownToExactMultiple(maxIndexSize, entrySize))
    }

    /* 内存映射 */
   
val len = raf.length()
    val idx = raf.getChannel.map(FileChannel.MapMode.READ_WRITE, 0, len)

    /* 蒋新创建的索引文件的position设置为0,从头开始写文件 */
   
if(newlyCreated)
      idx.position(0)
    else
     
// 对于原来就存在的索引文件,则将position移动到索引项的结束位置,防止数据覆盖
     
idx.position(roundDownToExactMultiple(idx.limit, entrySize))
    idx
 
} finally {
    CoreUtils.swallow(raf.close())
  }
}

 

// 添加指定offset/location对到索引中
def append(offset: Long, position: Int) {
  inLock(lock) {
    require(!isFull, "Attempt to append to a full index (size = " + _entries + ").")
    if (_entries == 0 || offset > _lastOffset) {
      debug("Adding index entry %d => %d to %s.".format(offset, position, file.getName))
      mmap.putInt((offset - baseOffset).toInt)
      mmap.putInt(position)
      _entries += 1
      _lastOffset = offset
      require(_entries * entrySize == mmap.position, entries + " entries but file position in index is " + mmap.position + ".")
    } else {
      throw new InvalidOffsetException("Attempt to append an offset (%d) to position %d no larger than the last offset appended (%d) to %s."
        .format(offset, entries, _lastOffset, file.getAbsolutePath))
    }
  }
}

 

查找索引:

// 查找那些大于或者等于给定的offsetoffsetposition
def lookup(targetOffset: Long): OffsetPosition = {
  maybeLock(lock) {
    val idx = mmap.duplicate // 创建一个副本
    val slot = indexSlotFor(idx, targetOffset, IndexSearchType.KEY)
    if(slot == -1)
      OffsetPosition(baseOffset, 0)
    else
      parseEntry(idx, slot).asInstanceOf[OffsetPosition]
  }
}

 

// 查找那些大于或者等于指定offset存储在哪一个slot上的
protected def indexSlotFor(idx: ByteBuffer, target: Long, searchEntity: IndexSearchEntity): Int = {
  // check if the index is empty
  if(_entries == 0)
    return -1

  // check if the target offset is smaller than the least offset
  if(compareIndexEntry(parseEntry(idx, 0), target, searchEntity) > 0)
    return -1

  // 二分查找算法
  var lo = 0
  var hi = _entries - 1
  while(lo < hi) {
    val mid = ceil(hi/2.0 + lo/2.0).toInt
    val found = parseEntry(idx, mid)
    val compareResult = compareIndexEntry(found, target, searchEntity)
    if(compareResult > 0)
      hi = mid - 1
    else if(compareResult < 0)
      lo = mid
    else
      return mid
  }
  lo
}

 

TimeIndex索引文件格式:它是映射时间戳和相对offset, 时间戳和相对offset作为entry,供占用12字节,时间戳占用8字节,相对offset占用4字节,这个索引也是稀疏索引,没有保存全部的消息的entry


OffsetIndex和TimeIndex分析_第2张图片

TimeIndex查找添加算法和OffsetIndex差不多,不再赘述


你可能感兴趣的:(大数据/kafka/源码,kafka,OffsetIndex,TimeIndex,源码)