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_ % kBloc