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在写日志时,对日志文件进行了划分为多个32K的文件块,每次读写日志时都以这样的每个32K为单位。这样进行处理以后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 > 0) {
        dest_->Append(Slice("\x00\x00\x00\x00\x00\x00", leftover));
      }
      block_offset_ = 0;
    }

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 > 0);
  return s;
}

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

posted on 2014-06-29 20:59  tgates  阅读(1630)  评论(0编辑  收藏

导航