从这一章节我开始讲具体组件和源代码, 阅读源代码的工具很多,leveldb的源代码不算大加上测试代码不过一万五千多行,一个比较好的源代码阅读工具是source insight。 不过要把文件名后缀.cc改成.cpp 建好一个项目 开始我们的征程。
首先我们讲log文件,write的第一站,也是系统容灾的常见方法。
log文件结构很简单,write依次将<key,value>按照log的format格式化写入 appand写入这里不存在随机i/O。
log文件的格式说明在/doc/log_format.txt下有具体描述
The log file contents are a sequence of 32KB blocks. The only exception is that the tail of the file 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]
这里重点讲一下type类型,在 Log_format.h中
enum RecordType {
// Zero is reserved for preallocated files
kZeroType = 0,
kFullType = 1,
// For fragments
kFirstType = 2,
kMiddleType = 3,
kLastType = 4
};
record是连续存储的,磁盘Block可能截断record 。Type用来标记record和当前Block的关系
KzeroType标识预留未被使用的BLock;
kFulltype表示此record所有部分都在当前Block下,
KFirstType表示此record的部分数据在当前block下,剩余数据在下一Block中(或者在下面连续几个Block中)
KMiddleType 标识此record的数据部分不仅仅全部占有此Block,而且在上一个和下一个Block也有数据
KlastType标识此record的数据部分在前一个Block(或者前几个连续Block中还有数据)
下面给出写log的操作,在Log_writer.cpp中 AddRecord函数是写日志的功能,实际写入磁盘,内部调用了EmitPhysicalRecord()函数,并且把CRC校验写入,这里面数据存储是小端形式
Status Writer::AddRecord(const Slice& slice) {
const char* ptr = slice.data();
size_t left = slice.size();
// Fragment the record if necessary and emit it. Note that if slice
// is empty, we still want to iterate once to emit a single
// zero-length record
Status s;
bool begin = true;
do {
const int leftover = kBlockSize - block_offset_; //计算剩余容量
assert(leftover >= 0);
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)); //如果容量小于record的数据头,record的最小值则
//填充0
}
block_offset_ = 0;
}
// Invariant: we never leave < kHeaderSize bytes in a block.
assert(kBlockSize - block_offset_ - kHeaderSize >= 0);
const size_t avail = kBlockSize - block_offset_ - kHeaderSize;
const size_t fragment_length = (left < avail) ? left : avail; //计算block剩余大小,以及本次log record可写入数据长度
RecordType type;
const bool end = (left == fragment_length); //end==true表示剩余大小为0,磁盘Block写满
if (begin && end) {
type = kFullType;
} else if (begin) {
type = kFirstType;
} else if (end) {
type = kLastType;
} else {
type = kMiddleType;
}
s = EmitPhysicalRecord(type, ptr, fragment_length); //这里写入磁盘 填入CRC校验
ptr += fragment_length;
left -= fragment_length;
begin = false; //下一block标记begin==false,表示未完待续哦。。。
} while (s.ok() && left > 0); // do..while结束
return s;
}
Log写很简单,读复杂的多,不过只有在数据库灾害恢复时候才会去读Log,精力有限没有去研究读操作。哈哈
刚刚也说的数据存储的大小端问题 leveldb才用小端模式
void EncodeFixed32(char* buf, uint32_t value) {
if (port::kLittleEndian) {
memcpy(buf, &value, sizeof(value));
} else {
buf[0] = value & 0xff;
buf[1] = (value >> 8) & 0xff;
buf[2] = (value >> 16) & 0xff;
buf[3] = (value >> 24) & 0xff;
}
}
这里我只想说位移运算"<<" 是算术位移,不能看做物理位移,否则大端的位移就是物理左移才能取到int的低位数(低位数在高字节) 这一点希望能够理解。