本系列《leveldb源码分析》共有22篇文章,这是第五篇。
分析完KV在内存中的存储,接下来就是操作日志。所有的写操作都必须先成功的append到操作日志中,然后再更新内存memtable。这样做有两点:
在各种高效的存储系统中,这已经是口水技术了。
在源码下的文档doc/log_format.txt中,作者详细描述了log格式:
The log file contents are a sequence of 32KB blocks.
The only exception is that the tail of thefile may contain a partial block.
Each block consists of a sequence of records:
block:= record* trailer?
record :=
checksum: uint32 // crc32c of type and data[] ; little-endian
length: uint16 // little-endian
type: uint8 // One of FULL,FIRST, MIDDLE, LAST
data: uint8[length]
A record never starts within the last six bytes of a block (since it won'tfit). Any leftover bytes here form thetrailer, which must consist entirely of zero bytes and must be skipped byreaders.
翻译过来就是: Leveldb把日志文件切分成了大小为32KB的连续block块,block由连续的log record组成,log record的格式为: 注意:CRC32, Length都是little-endian的。
Log Type有4种:FULL = 1、FIRST = 2、MIDDLE = 3、LAST = 4。FULL类型表明该log record包含了完整的user record;而user record可能内容很多,超过了block的可用大小,就需要分成几条log record,第一条类型为FIRST,中间的为MIDDLE,最后一条为LAST。也就是:
翻一下文档上的例子,考虑到如下序列的user records: A: length 1000 B: length 97270 C: length 8000
由于一条logrecord长度最短为7,如果一个block的剩余空间<=6byte,那么将**被填充为*空字*符串,另外长度为7的log record是不包括任何用户数据的。
写比读简单,而且写入决定了读,所以从写开始分析。有意思的是在写文件时,Leveldb使用了内存映射文件,内存映射文件的读写效率比普通文件要高,关于内存映射文件为何更高效,这篇文章写的不错: http://blog.csdn.net/mg0832058/article/details/5890688
其中涉及到的类层次比较简单,如图5.2-1:
注意Write类的成员type_crc_数组,这里存放的为Record Type预先计算的CRC32值,因为Record Type是固定的几种,为了效率。Writer类只有一个接口,就是AddRecord(),传入Slice参数,下面来看函数实现。首先取出slice的字符串指针和长度,初始化begin=true,表明是第一条log record。
const char* ptr = slice.data();
size_t left = slice.size();
bool begin = true;
然后进入一个do{}while循环,直到写入出错,或者成功写入全部数据,如下:
1
dest_->Append(Slice("\x00\x00\x00\x00\x00\x00",leftover));
block_offset_ = 0;
const size_t avail =kBlockSize - block_offset_ - kHeaderSize;
const size_t fragment_length = (left <avail) ? left : avail
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;
2
接下来看看EmitPhysicalRecord函数,这是实际写入的地方,涉及到log的存储格式。函数声明为:
StatusWriter::EmitPhysicalRecord(RecordType t, const char* ptr, size_t n) 参数ptr为用户record数据,参数n为record长度,不包含log header。
| CRC32 (4 byte) | payload length lower + high (2 byte) | type (1byte)|
char buf[kHeaderSize];
buf[4] = static_cast<char>(n& 0xff);
buf[5] =static_cast<char>(n >> 8);
buf[6] =static_cast<char>(t); // 计算record type和payload的CRC校验值
uint32_t crc = crc32c::Extend(type_crc_[t], ptr, n);
crc = crc32c::Mask(crc); // 空间调整
EncodeFixed32(buf, crc);
dest_->Append(Slice(buf,kHeaderSize));
s =dest_->Append(Slice(ptr, n));
s = dest_->Flush();
block_offset_ += kHeaderSize +n;
以上就是写日志的逻辑,很直观。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。