Leveldb源码解析第七篇【log】

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

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

log相关的源码文件有

db/log_format.h
db/log_reader.h
db/log_reader.cc
db/log_writer.h
db/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/

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值