1. 程式人生 > >leveldb學習:sstable(2)

leveldb學習:sstable(2)

size_t 保存 format net 理念 lin != inpu 層次

block寫入:block_builder

block.h和.cc裏定義了block的entry存儲格式和restart,提供了entry的查找接口以及叠代器。那麽怎樣往寫block裏寫entry呢?leveldb遵循面向對象的設計理念在block_builder類裏提供了相關接口。
BlockBuilder相關函數:

  • Add( )將entry順序寫入現有block數據塊的末端,排序工作在上層的函數完畢。
  • Finish( )。當block寫滿,完畢寫入重新啟動點數組和重新啟動點個數的寫入
  • Reset( )。重置block

sstable

已經知道。sstable是leveldb中持久化數據的文件格式。而總體來看。sstable由數據(data)和元信息(meta/index)組成,數據和源信息統一以block單位存儲。讀取時也按統一的邏輯讀取,總體的數據格式例如以下:
技術分享

  • data_block:實際存儲的kv數據
  • meta_block:當前版本號未實現
  • index_block:保存每一個data_block的last_key及其在sstable文件裏的索引

sstable讀取:table

/table/table.cc是有關將sstable讀取的操作:

 private:
  struct Rep;
  Rep* rep_;

定義了結構rep。並在table類設立一個指針成員。並在table::open( )函數完畢了rep_的實例化

Rep* rep = new Table::Rep;

rep結構:

struct Table::Rep {
  ~Rep() {
    delete
filter; delete [] filter_data; delete index_block; } Options options;//用戶設置 Status status;//狀態 RandomAccessFile* file;//文件讀操作流。主要成員有文件的名字,i節點和讀操作 uint64_t cache_id; FilterBlockReader* filter;//和meta_block有關。不用管 const char* filter_data;// BlockHandle metaindex_handle; // Handle to metaindex_block: saved from footer
Block* index_block; };

BlockHandle是一個用來指向block在文件裏位置的“指針”(裏面記錄的是文件偏移量),可參考format.h;
footer:文件末尾的固定長度的數據。保存著metaindex_block和index_block的索引信息(blockHandle),最後有8字節的magic校驗。

顯然footer信息的讀取對掌握整個table至關重要。


在table::open( )函數中就會從文件的末尾讀取footer:

......
  Slice footer_input;
  Status s = file->Read(size - Footer::kEncodedLength, Footer::kEncodedLength,
                        &footer_input, footer_space);
  if (!s.ok()) return s;

  Footer footer;
  s = footer.DecodeFrom(&footer_input);
  if (!s.ok()) return s;

......
  Block* index_block = NULL;
  if (s.ok()) {
    s = ReadBlock(file, opt, footer.index_handle(), &contents);
    if (s.ok()) {
      index_block = new Block(contents);
    }
  }

  ......
    rep->file = file;
    rep->metaindex_handle = footer.metaindex_handle();
    rep->index_block = index_block;

readBlock就是通過blockhandle讀取文件裏指定block的函數,定義在format.cc

Status ReadBlock(RandomAccessFile* file,
                 const ReadOptions& options,
                 const BlockHandle& handle,
                 BlockContents* result) {
  result->data = Slice();
  result->cachable = false;
  result->heap_allocated = false;
//blockcontents的初始化

  // Read the block contents as well as the type/crc footer.
  // See table_builder.cc for the code that built this structure.
  size_t n = static_cast<size_t>(handle.size());
  char* buf = new char[n + kBlockTrailerSize];
  Slice contents;
  Status s = file->Read(handle.offset(), n + kBlockTrailerSize, &contents, buf);
  if (!s.ok()) {
    delete[] buf;
    return s;
  }
  if (contents.size() != n + kBlockTrailerSize) {
    delete[] buf;
    return Status::Corruption("truncated block read");
  }

  // do something
  return Status::OK();
}

kBlockTrailerSize就是每一個block末端的五字節信息,包含壓縮標誌位和用於CRC校驗的開銷。do something 就是對提取到的內容分析。推斷有無壓縮。錯誤時返回狀態信息以及賦值result。

sstable寫入:table_builder

sstable寫如不須要關心排序,由於sstable的產生是由memtable dump或者compact時merge排序產生的,key的順序上層已經保證。


結構rep:

struct TableBuilder::Rep {
  Options options;
  Options index_block_options;
  WritableFile* file;//封裝了流操作的文件
  uint64_t offset;//寫入位置的偏移量
  Status status;
  BlockBuilder data_block; // 用於將entry寫入當前data_block
  BlockBuilder index_block;// 用於在index_block加入data_block的索引信息
  std::string last_key; //當前table中最後條目的key。寫入key要大於此。否則上層未提供排好序的entry
  int64_t num_entries;  //條目總數
  bool closed;          //table關閉標誌位。 Either Finish() or Abandon() has been called.
  FilterBlockBuilder* filter_block;

  bool pending_index_entry;//當前block為空時為true
  BlockHandle pending_handle;  // Handle to add to index block

  std::string compressed_output;

  Rep();
  }
void TableBuilder::Add(const Slice& key, const Slice& value)
{
  Rep* r = rep_;
  assert(!r->closed);
  if (!ok()) return;
  if (r->num_entries > 0) {
    assert(r->options.comparator->Compare(key, Slice(r->last_key)) > 0);
  }
    ......
  r->last_key.assign(key.data(), key.size());
  r->num_entries++;
  r->data_block.Add(key, value);

  const size_t estimated_block_size = r->data_block.CurrentSizeEstimate();
  if (estimated_block_size >= r->options.block_size) {
    Flush();
  }
}

leveldb把數據dump到磁盤,在內存中僅僅有一份block。當block滿了(大於options.block_size),就自己主動將此block寫入磁盤(Flush)。

寫入操作調用層次:

  • Add( ),寫入內存中的block,推斷block大小。決定是否寫入磁盤
  • Flush( )
  • WriteBlock( ),取block壓縮標誌位決定是否壓縮。寫入壓縮標誌位
  • WriteRawBlock( ),加入CRC,調用文件流寫入磁盤

leveldb學習:sstable(2)