日期:2014-05-16 浏览次数:20506 次
日志读取显然比写入要复杂,要检查checksum,检查是否有损坏等等,处理各种错误。
先来看看读取涉及到的类图,如图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_; // 读取的内容
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