天天看點

leveldb研究系列三——log檔案系統

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