Leveldb源码解析第七篇【log】

来源:互联网 发布:在淘宝上班销售怎么做 编辑:程序博客网 时间:2024/06/05 11:24

版权声明:本文为博主原创文章,未经博主允许不得转载。

这里的 log 非彼 log,这里的 log 是记录下用户的所有操作,防止设备异常导致 memtable 里面的数据丢失,用户在操作数据的时候首先会将操作写到 log 中,然后才会对数据进行操作

log相关的源码文件有

db/log_format.hdb/log_reader.hdb/log_reader.ccdb/log_writer.hdb/log_writer.cc

log_format

先看看log记录的格式

// log_format.h// log存放时会分为很多个块,块的大小是有限制的,当一行数据存储在一个块的结尾,而结尾剩下的空间不足以存下这条数据,那就会在几个块中存储这条记录,怎么将多个块中的数据合并成完整的数据呢,那就要看下面这个字段了。// 块中按record存储,一条数据可以分为多个record,每个record中会有一个type来记录这个record的类型enum RecordType {  kZeroType = 0,  kFullType = 1,    // 表示这个record是一条完整数据  kFirstType = 2,   // 表示这个record是一条数据的开始  kMiddleType = 3,  // 表示这个record是一条数据的中间部分,后面还有数据  kLastType = 4     // 表示这个record是一条数据的结尾};static const int kMaxRecordType = kLastType;// 块大小static const int kBlockSize = 32768;// record头部长度static const int kHeaderSize = 4 + 2 + 1;

log_writer.h

理解log的结构就先看看数据是怎么写入到log中的

// log_write.cc// 初始化数据type循环冗余校验static void InitTypeCrc(uint32_t* type_crc) {  for (int i = 0; i <= kMaxRecordType; i++) {    char t = static_cast<char>(i);    type_crc[i] = crc32c::Value(&t, 1);  }}Status Writer::AddRecord(const Slice& slice) {  const char* ptr = slice.data();  // 表示还剩下多少的数据没有写入log中  size_t left = slice.size();  Status s;  bool begin = true;  do {    // 块中还剩下多少空间    const int leftover = kBlockSize - block_offset_;    assert(leftover >= 0);    // 如果剩下的空间不足7个字节,说明剩下的空间连record的头部都存储不下,用"\x00\x00\x00\x00\x00\x00"在填充剩下的空间    if (leftover < kHeaderSize) {      // Switch to a new block      if (leftover > 0) {        // Fill the trailer (literal below relies on kHeaderSize being 7)        assert(kHeaderSize == 7);        dest_->Append(Slice("\x00\x00\x00\x00\x00\x00", leftover));      }      block_offset_ = 0;    }    // Invariant: we never leave < kHeaderSize bytes in a block.    assert(kBlockSize - block_offset_ - kHeaderSize >= 0);    // block剩余空间减去一条record头部占用的空间,剩下是真正可以存储数据的空间    const size_t avail = kBlockSize - block_offset_ - kHeaderSize;    // 可以存储数据的空间大小和数据大小比较,哪个小存储哪个    const size_t fragment_length = (left < avail) ? left : avail;    RecordType type;    // 如果数据大小 小于 可以存储数据的空间大小,那么end就为true;反之    const bool end = (left == fragment_length);    if (begin && end) {      // 第一次进来begin是true,如果块可以存储,那么end也为true,那么这条数据在这个块中是完整存储的      type = kFullType;    } else if (begin) {      // 第一次进来begin是true,如果块不够存储,那么end也为false,那么这条数据在这个块中只存储了这条数据的前面部分      type = kFirstType;    } else if (end) {      // 不是第一次进来begin是false,如果块够存储,那么end也为true,那么这条数据在这个块中是后面部分      type = kLastType;    } else {      // 又不是第一次进来,块又存储不够,那么这个块就只存储了这条数据的中间部分      type = kMiddleType;    }    // 将record写入磁盘    s = EmitPhysicalRecord(type, ptr, fragment_length);    ptr += fragment_length;    left -= fragment_length;    begin = false;  } while (s.ok() && left > 0);  return s;}// 将record写入磁盘Status Writer::EmitPhysicalRecord(RecordType t, const char* ptr, size_t n) {  assert(n <= 0xffff);  // Must fit in two bytes  assert(block_offset_ + kHeaderSize + n <= kBlockSize);  char buf[kHeaderSize];  // 块最大为32768,这个数字两个字节可以存下,record head第5和第6位用来存放record大小  buf[4] = static_cast<char>(n & 0xff);  buf[5] = static_cast<char>(n >> 8);  // 一条record的head第7位存放RecordType  buf[6] = static_cast<char>(t);  // Compute the crc of the record type and the payload.  uint32_t crc = crc32c::Extend(type_crc_[t], ptr, n);  crc = crc32c::Mask(crc);                 // Adjust for storage  // head前4位存储crc  EncodeFixed32(buf, crc);  // Write the header and the payload  // 在文件中写入head数据  Status s = dest_->Append(Slice(buf, kHeaderSize));  if (s.ok()) {    // 再将数据写入到文件中    s = dest_->Append(Slice(ptr, n));    if (s.ok()) {      // 如果都写入成功,文件刷新      s = dest_->Flush();    }  }  block_offset_ += kHeaderSize + n;  return s;}

log 还是很简单的,主要搞明白 record 的格式就可以了

【作者:antonyxu https://antonyxux.github.io/ 】