從這一章節我開始講具體元件和源代碼, 閱讀源代碼的工具很多,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的低位數(低位數在高位元組) 這一點希望能夠了解。