推荐结合 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