OffsetIndex和TimeIndex分析

来源:互联网 发布:中国平安管培生 知乎 编辑:程序博客网 时间:2024/04/29 00:24

为了提高查找消息的性能,为每一个日志文件添加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


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))    }  }}

 

查找索引:

// 查找那些大于或者等于给定的offsetoffsetpositiondef 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 -// check if the target offset is smaller than the least offset  if(compareIndexEntry(parseEntry(idx, 0), target, searchEntity) > 0)    return -// 二分查找算法  var lo = var hi = _entries - 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



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