leveldb源码分析--日志

时间:2023-03-09 06:58:30
leveldb源码分析--日志

我们知道在一个数据库系统中为了保证数据的可靠性,我们都会记录对系统的操作日志。日志的功能就是用来在系统down掉的时候对数据进行恢复,所以日志系统对一个要求可靠性的存储系统是极其重要的。接下来我们分析leveldb的日志,首先在leveldb源码目录中有doc/log_format.txt,这个文件详细的描述了leveldb的日志格式:

record :=
checksum: uint32 // crc32c of type and data[] ; little-endian
length: uint16 // little-endian
type: uint8 // One of FULL,FIRST, MIDDLE, LAST
data: uint8[length]

如下图(图片引用):

leveldb源码分析--日志

leveldb在写日志时,对日志文件进行了划分为多个32K的文件块,每次读写日志时都以这样的每个32K为单位。这样进行处理以后leveldb的日志文件的大致组成就可以看做为如下的形式:leveldb源码分析--日志

那么我们初步描述一下Log的写入情况为:

当要写入一条首先判断当前block中是否足够存放该条日志

S1.如果足够那么直接安装格式写入;

S2.如果不够那么计算出去头以外可以存放多少内容,将内容组装为FIRST的Log typpe写入;然后新取一个块判断是否足够存放剩下的日志数据

while(数据未写完)

S21. 如果足够就组装为LAST的形式写入;

S22. 如果仍然不够就组装为MIDDLE的形式写入

所以就容易理解这里的FULL,FIRST,MIDDLE和LAST了:

FULL:一条完整的日志被写到block

FIRST:一条日志,但是当前block无法完全写入,有部分数据被写到了下一个block,当前block的数据只是日志的开始(第一)部分

MIDDLE:该日志内容是接着前一个block里面的最后一条日志的继续,而且本block还无法完全写完,在下一个block中继续有该条日志的数据

LAST:    之前block的未写完的日志的最后一部分;

另外需要注意的就是根据前面的描述我们可以想象得到一个block在写入一部分数据以后会剩下部分空间,这个空间可能是大于7byte,等于7byte,小于7byte;这里为什么要以7byte为分界呢?日志记录的header(crc|length|type)长度为7,如果超过7就至少可以存一个FISRT的部分日志记录,而等于7就刚好存一个header,少于7就连header都存不了。LOG也正是基于这样的原因,小于7时就补充”\0”,7就存一个空header。我们来看看代码逻辑

Status Writer::AddRecord(const Slice& slice) {
bool begin = true;
// 循环向日志文件写,直到写完为止
do {
const int leftover = kBlockSize - block_offset_;
if (leftover < kHeaderSize) {// 小于 7 byte (header size )填充 0x0
if (leftover > ) {
dest_->Append(Slice("\x00\x00\x00\x00\x00\x00", leftover));
}
block_offset_ = ;
} const size_t avail = kBlockSize - block_offset_ - kHeaderSize;
const size_t fragment_length = (left < avail) ? left : avail; RecordType type;
const bool end = (left == fragment_length);{// 判断本段能否写完
if (begin && end) { // 开始结束都在本block
type = kFullType;
} else if (begin) { // 开始在本block,结束不在
type = kFirstType;
} else if (end) { // 结束在,开始不在
type = kLastType;
} else { // 开始结束都不在本block
type = kMiddleType;
}
// encode 然后再写入文件中
s = EmitPhysicalRecord(type, ptr, fragment_length);
ptr += fragment_length;
left -= fragment_length;
begin = false;
} while (s.ok() && left > );
return s;
}

读日志的代码在log_reader.cc中,代码的逻辑比写负责很多,主要是读入时会需要增加很多错误处理相关的内容,具体的代码不在罗列,理解了日志文件的格式以后的很容易就能读懂。当然同时其异常处理的逻辑也是我们码农们学习的材料,理解一下高手们是如何进行各种错误处理的。