天天看點

log::Writer-levelDB源碼解析

log::Writer-levelDB源碼解析

writer的工作就是将上層拼裝好的資料,按照log format的格式,進行二次拼裝,然後寫進檔案裡面。

【資料成員介紹】

block_offset_:目前block的偏移位,通過它就能知道目前資料寫到這一塊的什麼位置了;

dest_:增加記錄,就是将資料追加到dest_;

type_crc_:因為每個record都會有一個header,而header裡面就會有checksum,在調用這個類的構造函數的時候,就會預先将RecordType的crc生成好,然後儲存在type_crc_;

【方法成員介紹】

Status Writer::AddRecord(const Slice& slice)

作用:将記錄按照log format的方式組裝,并刷到磁盤。

  Status Writer::AddRecord(const Slice& slice) {

     //擷取資料的開始位置

    const char* ptr = slice.data();

    //資料的剩餘大小

    size_t left = slice.size();

    Status s;

    //資料在這塊中開始

    bool begin = true;

    do {

    //目前塊的剩餘大小

      const int leftover = kBlockSize - block_offset_;

      assert(leftover >= 0);

        //當塊的剩餘大小不足header的大小時,用\x00填充塊剩餘的空間,并重新開始一個新的block,block_offset_=0

      if (leftover < kHeaderSize) {

        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;

      }

      assert(kBlockSize - block_offset_ - kHeaderSize >= 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);

        //資料在這塊中開始,也在這塊中結束,那麼資料整個都在這塊中,那麼類型自然就是kFullType

      if (begin && end) {

        type = kFullType;

         //資料隻是在這塊中開始,并沒有結束,那麼資料整個都在這塊中,那麼類型自然就是kFirstType

      } else if (begin) {

        type = kFirstType;

         //資料隻是在這塊中結束,并沒有開始,那麼資料整個都在這塊中,那麼類型自然就是kLastType

      } else if (end) {

        type = kLastType;

       //資料在目前塊即沒有開始,也沒有結束,那自然就是中間了kMiddleType

      } else {

        type = kMiddleType;

      }

      s = EmitPhysicalRecord(type, ptr, fragment_length);

      ptr += fragment_length;

      left -= fragment_length;

      begin = false;

    } while (s.ok() && left > 0);

    return s;

  }

這個方法的重點就是,始終把目前塊當做參照物,注意目前block在record中的type和block_offset_就可以了;

每次寫一個record,要麼是把整個record寫完,要麼是把整個block寫完。

Status Writer::EmitPhysicalRecord(RecordType t, const char* ptr, size_t n)

作用:根據本次寫的資料,生成header,并将資料刷到磁盤上。

  Status Writer::EmitPhysicalRecord(RecordType t, const char* ptr, size_t n) {

    assert(n <= 0xffff);

    assert(block_offset_ + kHeaderSize + n <= kBlockSize);

    // 填充header

    char buf[kHeaderSize];

    //本次寫入data的大小,即fragment_length,小端方式儲存

    buf[4] = static_cast<char>(n & 0xff);

    buf[5] = static_cast<char>(n >> 8);

    //RecordType

    buf[6] = static_cast<char>(t);

    // 計算資料的crc

    uint32_t crc = crc32c::Extend(type_crc_[t], ptr, n);

    crc = crc32c::Mask(crc);  

    //将校驗碼插入header中

    EncodeFixed32(buf, crc);

    // 将header和資料追加到dest_,并寫到磁盤

    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;

  }