日期:2014-05-16  浏览次数:20469 次

Leveldb源码分析--6

5 操作Log 2

 

5.3 读日志

日志读取显然比写入要复杂,要检查checksum,检查是否有损坏等等,处理各种错误。

5.3.1 类层次

先来看看读取涉及到的类图,如图5.3-1。

Reader主要用到了两个接口,一个是汇报错误的Reporter,另一个是log文件读取类SequentialFile。

> Reporter的接口只有一个:void Corruption(size_t bytes,const Status& status);

> SequentialFile有两个接口:

    Status Read(size_t n, Slice* result, char* scratch);

    Status Skip(uint64_t n);

说明下,Read接口有一个*result参数传递结果就行了,为何还有一个*scratch呢,这个就和Slice相关了。它的字符串指针是传入的外部char*指针,自己并不负责内存的管理与分配。因此Read接口需要调用者提供一个字符串指针,实际存放字符串的地方。


图5.3-1

Reader类有几个成员变量,需要注意:

  bool eof_;   // 上次Read()返回长度< kBlockSize,暗示到了文件结尾EOF

  uint64_t last_record_offset_; // 函数ReadRecord返回的上一个record的偏移

  uint64_t end_of_buffer_offset_; // 当前的读取偏移

  uint64_t const initial_offset_; // 偏移,从哪里开始读取第一条record

  Slice   buffer_; // 读取的内容

5.3.2日志读取流程

Reader只有一个接口,那就是ReadRecord,下面来分析下这个函数。

S1 根据initial offset跳转到调用者指定的位置,开始读取日志文件。跳转就是直接调用SequentialFile的Seek接口。

另外,需要先调整调用者传入的initialoffset参数,调整和跳转逻辑在SkipToInitialBlock函数中。

  if (last_record_offset_ <initial_offset_) { // 当前偏移 < 指定的偏移,需要Seek

     if (!SkipToInitialBlock()) returnfalse;

  }

下面的代码是SkipToInitialBlock函数调整read offset的逻辑:

  // 计算在block内的偏移位置,并圆整到开始读取block的起始位置

  size_t offset_in_block =initial_offset_ % kBlockSize;

  uint64_t block_start_location =initial_offset_ - offset_in_block;

  // 如果偏移在最后的6byte里,肯定不是一条完整的记录,跳到下一个block

  if (offset_in_block >kBlockSize - 6) {

    offset_in_block = 0;

    block_start_location +=kBlockSize;

  }

  end_of_buffer_offset_ =block_start_location; // 设置读取偏移

  if (block_start_location > 0)  file_->Skip(block_start_location); // 跳转

首先计算出在block内的偏移位置,然后圆整到要读取block的起始位置。开始读取日志的时候都要保证读取的是完整的block,这就是调整的目的。

同时成员变量end_of_buffer_offset_记录了这个值,在后续读取中会用到。

S2在开始while循环前首先初始化几个标记:

// 当前是否在fragment内,也就是遇到了FIRST 类型的record