leveldb源码学习之日志 log 写入

推荐结合 leveldb-handbook 来阅读源码

为了防止写入内存的数据库因为进程异常、系统掉电等情况发生丢失,leveldb在写内存之前会将本次写操作的内容写入日志文件中。

 

在leveldb中,有两个memtable,以及对应的两份日志文件。其中一个memtable是可读写的,当这个db的数据量超过预定的上限时,便会转换成一个不可读的immutable,与此同时,与之对应的日志文件也变成一份frozen log。

而新生成的immutable 则会由后台的minor compaction进程将其转换成一个sstable文件进行持久化,持久化完成,与之对应的frozen log被删除。

日志结构

写入流程

 

 

写日志主要由log_writer.h 中的Writer类完成

  • 用于将数据写入内存中的block

  • 若数据长度超出当前block大小,需要分片

  • 数据的header固定占用7个字节

log_writer.h

#ifndef STORAGE_LEVELDB_DB_LOG_WRITER_H_
#define STORAGE_LEVELDB_DB_LOG_WRITER_H_

#include <stdint.h>

#include "db/log_format.h"
#include "leveldb/slice.h"
#include "leveldb/status.h"

namespace leveldb {

class WritableFile;

namespace log {

class Writer {
 public:
  // 创建writer,用于将数据追加到"*dest".
  // "*dest" 必须初始化为空.
  // "*dest" 在 Writer 使用中必须保证有效.
  explicit Writer(WritableFile* dest);

  // 创建writer,用于将数据追加到"*dest".
  // "*dest" 必须初始化为长度 "dest_length".
  // "*dest" 在 Writer 使用中必须保证有效.
  Writer(WritableFile* dest, uint64_t dest_length);

  Writer(const Writer&) = delete; // 禁用 拷贝构造函数
  Writer& operator=(const Writer&) = delete; // 禁用赋值函数

  ~Writer();

  Status AddRecord(const Slice& slice);

 private:
  Status EmitPhysicalRecord(RecordType type, const char* ptr, size_t length);

  WritableFile* dest_;
  int block_offset_;  // 数据块中当前偏移

  // 对所有record类型的crc32c值. 提前计算好以减少header中record类型的crc计算开销
  uint32_t type_crc_[kMaxRecordType + 1];
};

}  // namespace log
}  // namespace leveldb

#endif  // STORAGE_LEVELDB_DB_LOG_WRITER_H_

 

log_writer.cc

#include "db/log_writer.h"

#include <stdint.h>

#include "leveldb/env.h"
#include "util/coding.h"
#include "util/crc32c.h"

namespace leveldb {
namespace log {

// 计算每种类型(0,1,2,3,4)转为字符后的crc32
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);
  }
}

Writer::Writer(WritableFile* dest) : dest_(dest), block_offset_(0) {
  InitTypeCrc(type_crc_);
}

Writer::Writer(WritableFile* dest, uint64_t dest_length)
    : dest_(dest), block_offset_(dest_length % kBlockSize) {
  InitTypeCrc(type_crc_);
}

Writer::~Writer() = default;

// 往数据块中插入记录
Status Writer::AddRecord(const Slice& slice) {
  const char* ptr = slice.data();
  size_t left = slice.size();

  // 将待插入记录按需分片并插入. 若记录为空,仍会插入一条空记录
  Status s;
  bool begin = true; // 标记是否数据开头的分片
  do {
    const int leftover = kBlockSize - block_offset_; // 当前数据块所剩空间
    assert(leftover >= 0);
    if (leftover < kHeaderSize) { // 分片header组成为:校验和(4字节), 长度(2字节),类型 (1 byte). 这里指剩余空间放不下一个分片的header,直接将剩余空间填充为0,切到新的数据块
      if (leftover > 0) {
        static_assert(kHeaderSize == 7, "");
        dest_->Append(Slice("\x00\x00\x00\x00\x00\x00", leftover)); // 这里会调用falestats的Append方法,并调用内存管理器arena 分配一块新的block。
      }
      block_offset_ = 0;
    }

    assert(kBlockSize - block_offset_ - kHeaderSize >= 0);

    // header必须占用7个字节
    const size_t avail = kBlockSize - block_offset_ - kHeaderSize;
    const size_t fragment_length = (left < avail) ? left : avail; // 计算当前分片大小,超出当前block剩余的空间要切断

    RecordType type;
    const bool end = (left == fragment_length); // 数据剩余长度等于当前分片大小,表示最后一块分片
    if (begin && end) { // 是第一块且最后一块分片,表示全写入
      type = kFullType;
    } else if (begin) { // 第一块分片
      type = kFirstType;
    } else if (end) { // 最后一块分片
      type = kLastType;
    } else {
      type = kMiddleType; // 中间的分片
    }

    s = EmitPhysicalRecord(type, ptr, fragment_length);
    ptr += fragment_length;
    left -= fragment_length;
    begin = false; // 第一块已经写了
  } while (s.ok() && left > 0);
  return s;
}

// 将分片写入数据块
Status Writer::EmitPhysicalRecord(RecordType t, const char* ptr,
                                  size_t length) {
  assert(length <= 0xffff);  // 长度必须可由2个字节表示
  assert(block_offset_ + kHeaderSize + length <= kBlockSize); // 必须在当前block可以写完

  // 初始化header
  char buf[kHeaderSize];
  buf[4] = static_cast<char>(length & 0xff); // 前4字节为校验和,4,5字节为长度,末字节为类型
  buf[5] = static_cast<char>(length >> 8);
  buf[6] = static_cast<char>(t);

  // 根据分片类型和内容 计算校验和crc.
  uint32_t crc = crc32c::Extend(type_crc_[t], ptr, length);
  crc = crc32c::Mask(crc);  // Adjust for storage
  EncodeFixed32(buf, crc);

  // 写入header 和内容
  Status s = dest_->Append(Slice(buf, kHeaderSize));
  if (s.ok()) {
    s = dest_->Append(Slice(ptr, length));
    if (s.ok()) {
      s = dest_->Flush(); // 刷盘
    }
  }
  block_offset_ += kHeaderSize + length;
  return s;
}

}  // namespace log
}  // namespace leveldb

 

 

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值