版权声明:本文为博主原创文章,未经博主允许不得转载。
这里的 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/ 】