1. 程式人生 > >Leveldb原始碼分析--8

Leveldb原始碼分析--8

6 SSTable之2

6.4 建立sstable檔案

瞭解了sstable檔案的儲存格式,以及Data Block的組織,下面就可以分析如何建立sstable檔案了。相關程式碼在table_builder.h/.cc以及block_builder.h/.cc(構建Block)中。

6.4.1 TableBuilder類

構建sstable檔案的類是TableBuilder,該類提供了幾個有限的方法可以用來新增k/v對,Flush到檔案中等等,它依賴於BlockBuilder來構建Block。

TableBuilder的幾個介面說明下:

> void Add(const Slice& key, const Slice& value),向當前正在構建的表新增新的{key, value}對,要求根據Option指定的Comparator,key必須位於所有前面新增的key之後;

> void Flush(),將當前快取的k/v全部flush到檔案中,一個高階方法,大部分的client不需要直接呼叫該方法;

> void Finish(),結束表的構建,該方法被呼叫後,將不再會使用傳入的WritableFile;

> void Abandon(),結束表的構建,並丟棄當前快取的內容,該方法被呼叫後,將不再會使用傳入的WritableFile;【只是設定closed為true,無其他操作】

一旦Finish()/Abandon()方法被呼叫,將不能再次執行Flush或者Add操作。

下面來看看涉及到的類,如圖6.3-1所示。

圖6.3-1

其中WritableFile和op log一樣,使用的都是記憶體對映檔案。Options是一些呼叫者可設定的選項。

TableBuilder只有一個成員變數Rep* rep_,實際上Rep結構體的成員就是TableBuilder所有的成員變數;這樣做的目的,可能是為了隱藏其內部細節。Rep的定義也是在.cc檔案中,對外是透明的。

簡單解釋下成員的含義:

  1. Options options;   // data block的選項
  2. Options index_block_options; // index block的選項
  3. WritableFile* file;  // sstable檔案
  4. uint64_t offset; // 要寫入data block在sstable檔案中的偏移,初始0
  5. Status status; //當前狀態-初始ok
  6. BlockBuilder data_block; //當前操作的data block
  7. BlockBuilder index_block; // sstable的index block
  8. std::string last_key; //當前data block最後的k/v對的key
  9. int64_t num_entries; //當前data block的個數,初始0
  10. bool closed;          //呼叫了Finish() or Abandon(),初始false
  11. FilterBlockBuilder*filter_block; //根據filter資料快速定位key是否在block中
  12. bool pending_index_entry; //見下面的Add函式,初始false
  13. BlockHandle pending_handle; //新增到index block的data block的資訊
  14. std::string compressed_output;//壓縮後的data block,臨時儲存,寫入後即被清空

Filter block是儲存的過濾器資訊,它會儲存{key, 對應data block在sstable的偏移值},不一定是完全精確的,以快速定位給定key是否在data block中。

下面分析如何向sstable中新增k/v對,建立並持久化sstable。其它函式都比較簡單,略過。另外對於Abandon,簡單設定closed=true即返回。

6.4.2 新增k/v對

這是通過方法Add(constSlice& key, const Slice& value)完成的,沒有返回值。下面分析下函式的邏輯:

S1 首先保證檔案沒有close,也就是沒有呼叫過Finish/Abandon,以及保證當前status是ok的;如果當前有快取的kv對,保證新加入的key是最大的。

  1. Rep* r = rep_;  
  2. assert(!r->closed);  
  3. if (!ok()) return;  
  4. if (r->num_entries > 0) {  
  5.    assert(r->options.comparator->Compare(key, Slice(r->last_key))> 0);  
  6. }  

S2 如果標記r->pending_index_entry為true,表明遇到下一個data block的第一個k/v,根據key調整r->last_key,這是通過Comparator的FindShortestSeparator完成的。

  1. if (r->pending_index_entry) {  
  2.    assert(r->data_block.empty());  
  3.    r->options.comparator->FindShortestSeparator(&r->last_key,key);  
  4.    std::string handle_encoding;  
  5.    r->pending_handle.EncodeTo(&handle_encoding);  
  6.    r->index_block.Add(r->last_key, Slice(handle_encoding));  
  7.    r->pending_index_entry =false;  
  8. }  

接下來將pending_handle加入到index block中{r->last_key, r->pending_handle’sstring}。最後將r->pending_index_entry設定為false。

值得講講pending_index_entry這個標記的意義,見程式碼註釋:

直到遇到下一個databock的第一個key時,我們才為上一個datablock生成index entry,這樣的好處是:可以為index使用較短的key;比如上一個data block最後一個k/v的key是"the quick brown fox",其後繼data block的第一個key是"the who",我們就可以用一個較短的字串"the r"作為上一個data block的index block entry的key。

簡而言之,就是在開始下一個datablock時,Leveldb才將上一個data block加入到index block中。標記pending_index_entry就是幹這個用的,對應data block的index entry資訊就儲存在(BlockHandle)pending_handle。

S3 如果filter_block不為空,就把key加入到filter_block中。

  1. if (r->filter_block != NULL) {  
  2.    r->filter_block->AddKey(key);  
  3. }  

S4 設定r->last_key = key,將(key, value)新增到r->data_block中,並更新entry數。

  1. r->last_key.assign(key.data(), key.size());  
  2. r->num_entries++;  
  3. r->data_block.Add(key,value);  

S5 如果data block的個數超過限制,就立刻Flush到檔案中。

  1. const size_testimated_block_size = r->data_block.CurrentSizeEstimate();  
  2. if (estimated_block_size >=r->options.block_size) Flush();  

6.4.3 Flush檔案

該函式邏輯比較簡單,直接見程式碼如下:

  1. Rep* r = rep_;  
  2. assert(!r->closed); // 首先保證未關閉,且狀態ok
  3. if (!ok()) return;  
  4. if (r->data_block.empty())return// data block是空的
  5. // 保證pending_index_entry為false,即data block的Add已經完成
  6. assert(!r->pending_index_entry);  
  7. // 寫入data block,並設定其index entry資訊—BlockHandle物件
  8. WriteBlock(&r->data_block, &r->pending_handle);  
  9. //寫入成功,則Flush檔案,並設定r->pending_index_entry為true,
  10. //以根據下一個data block的first key調整index entry的key—即r->last_key
  11. if (ok()) {  
  12.   r->pending_index_entry =true;  
  13.   r->status =r->file->Flush();  
  14. }  
  15. if (r->filter_block != NULL){ //將data block在sstable中的便宜加入到filter block中
  16.   r->filter_block->StartBlock(r->offset); // 並指明開始新的data block
  17. }  

6.4.4 WriteBlock函式

在Flush檔案時,會呼叫WriteBlock函式將data block寫入到檔案中,該函式同時還設定data block的index entry資訊。原型為:

void WriteBlock(BlockBuilder* block, BlockHandle* handle)

該函式做些預處理工作,序列化要寫入的data block,根據需要壓縮資料,真正的寫入邏輯是在WriteRawBlock函式中。下面分析該函式的處理邏輯。

S1 獲得block的序列化資料Slice,根據配置引數決定是否壓縮,以及根據壓縮格式壓縮資料內容。對於Snappy壓縮,如果壓縮率太低<12.5%,還是作為未壓縮內容儲存。

BlockBuilder的Finish()函式將data block的資料序列化成一個Slice。

  1. Rep* r = rep_;  
  2. Slice raw = block->Finish(); // 獲得data block的序列化字串
  3. Slice block_contents;  
  4. CompressionType type =r->options.compression;  
  5. switch (type) {  
  6.   case kNoCompression: block_contents= raw; break// 不壓縮
  7.   case kSnappyCompression: { // snappy壓縮格式
  8.     std::string* compressed =&r->compressed_output;  
  9.     if(port::Snappy_Compress(raw.data(), raw.size(), compressed) &&  
  10.         compressed->size()< raw.size() - (raw.size() / 8u)) {  
  11.         block_contents =*compressed;  
  12.     } else { // 如果不支援Snappy,或者壓縮率低於12.5%,依然當作不壓縮儲存
  13.       block_contents = raw;  
  14.       type = kNoCompression;  
  15.     }  
  16.     break;  
  17.   }  
  18. }  

S2 將data內容寫入到檔案,並重置block成初始化狀態,清空compressedoutput。

  1. WriteRawBlock(block_contents,type, handle);  
  2. r->compressed_output.clear();  
  3. block->Reset();  

6.4.5 WriteRawBlock函式

在WriteBlock把準備工作都做好後,就可以寫入到sstable檔案中了。來看函式原型:

void WriteRawBlock(const Slice& data, CompressionType, BlockHandle*handle);

函式邏輯很簡單,見程式碼。

  1. Rep* r = rep_;  
  2. handle->set_offset(r->offset); // 為index設定data block的handle資訊
  3. handle->set_size(block_contents.size());  
  4. nbsp;r->status =r->file->Append(block_contents); // 寫入data block內容
  5. if (r->status.ok()) {// 寫入1byte的type和4bytes的crc32
  6.   chartrailer[kBlockTrailerSize];  
  7.   trailer[0] = type;  
  8.   uint32_t crc = crc32c::Value(block_contents.data(),block_contents.size());  
  9.   crc = crc32c::Extend(crc, trailer, 1);  // Extend crc tocover block type
  10.   EncodeFixed32(trailer+1, crc32c::Mask(crc));  
  11.   r->status =r->file->Append(Slice(trailer, kBlockTrailerSize));  
  12.   if (r->status.ok()) { // 寫入成功更新offset-下一個data block的寫入偏移
  13.     r->offset +=block_contents.size() + kBlockTrailerSize;  
  14.   }  
  15. }  

6.4.6 Finish函式

呼叫Finish函式,表明呼叫者將所有已經新增的k/v對持久化到sstable,並關閉sstable檔案。

該函式邏輯很清晰,可分為5部分。

S1 首先呼叫Flush,寫入最後的一塊data block,然後設定關閉標誌closed=true。表明該sstable已經關閉,不能再新增k/v對。

  1. Rep* r = rep_;  
  2. Flush();  
  3. assert(!r->closed);  
  4. r->closed = true;  

BlockHandle filter_block_handle,metaindex_block_handle, index_block_handle;

S2 寫入filter block到檔案中

  1. if (ok() &&r->filter_block != NULL) {  
  2.   WriteRawBlock(r->filter_block->Finish(), kNoCompression,&filter_block_handle);  
  3. }  

S3 寫入meta index block到檔案中

如果filterblock不為NULL,則加入從"filter.Name"到filter data位置的對映。通過meta index block,可以根據filter名字快速定位到filter的資料區。

  1. if (ok()) {  
  2.   BlockBuildermeta_index_block(&r->options);  
  3.   if (r->filter_block !=NULL) {  
  4.     //加入從"filter.Name"到filter data位置的對映
  5.     std::string key ="filter.";  
  6.     key.append(r->options.filter_policy->Name());  
  7.     std::string handle_encoding;  
  8.     filter_block_handle.EncodeTo(&handle_encoding);  
  9.     meta_index_block.Add(key,handle_encoding);  
  10.   }  
  11.   // TODO(postrelease): Add stats and other metablocks
  12.   WriteBlock(&meta_index_block, &metaindex_block_handle);  
  13. }  

S4 寫入index block,如果成功Flush過data block,那麼需要為最後一塊data block設定index block,並加入到index block中。

  1. if (ok()) {  
  2.   if (r->pending_index_entry){ // Flush時會被設定為true
  3.     r->options.comparator->FindShortSuccessor(&r->last_key);  
  4.     std::string handle_encoding;  
  5.     r->pending_handle.EncodeTo(&handle_encoding);  
  6.     r->index_block.Add(r->last_key, Slice(handle_encoding)); // 加入到index block中
  7.     r->pending_index_entry =false;  
  8.   }  
  9.   WriteBlock(&r->index_block, &index_block_handle);  
  10. }  

S5 寫入Footer。

  1. if (ok()) {  
  2.   Footer footer;  
  3.   footer.set_metaindex_handle(metaindex_block_handle);  
  4.   footer.set_index_handle(index_block_handle);  
  5.   std::string footer_encoding;  
  6.   footer.EncodeTo(&footer_encoding);  
  7.   r->status =r->file->Append(footer_encoding);  
  8.   if (r->status.ok()) {  
  9.     r->offset +=footer_encoding.size();  
  10.   }  
  11. }  

整個寫入流程就分析完了,對於Datablock和Filter Block的操作將在Data block和Filter Block中單獨分析,下面的讀取相同。

相關推薦

Leveldb原始碼分析--8

6 SSTable之2 6.4 建立sstable檔案 瞭解了sstable檔案的儲存格式,以及Data Block的組織,下面就可以分析如何建立sstable檔案了。相關程式碼在table_builder.h/.cc以及block_builder.h/.cc(

elasticSearch6原始碼分析(8)RepositoriesModule模組

1.RepositoriesModule概述 Sets up classes for Snapshot/Restore 1.1 snapshot概述 A snapshot is a backup taken from a running Elasticsearch cluster. Yo

janusgraph原始碼分析8-底層互動

反向分析 cassandra 寫資料 API cassandra 的結構類似 bigtable ,資料實際上是多層巢狀的 map,第一個 key 是 rowkey,第二層key 是 columnFamily,第三層key 是 column,第四層(也可以忽略)

以太坊之LevelDB原始碼分析

最近研究以太坊的LevelDB使用,看了看程式碼,大致介紹下使用流程(網上介紹的leveldb大多是c++版本的,以太坊使用的是go語言版本的),我使用的是mac book開發環境。介紹中會忽略一些細節,如有重要遺漏或者錯誤歡迎指出。讀此篇文章預設leveldb的基本知識都瞭

LevelDB原始碼分析之九:env

考慮到移植以及靈活性,LevelDB將系統相關的處理(檔案/程序/時間)抽象成Evn,使用者可以自己實現相應的介面,作為option的一部分傳入,預設使用自帶的實現。  env.h中聲明瞭: 虛基類env,在env_posix.cc中,派生類PosixEnv繼承自env

Android 7.0 Gallery相簿原始碼分析8

在Android 7.0 Gallery相簿原始碼分析3 - 資料載入及顯示流程一文最後講了AlbumSetSlidingWindow的onContentChanged方法,專輯縮圖和縮圖下面的label的載入就是在此方法中完成的 public

Leveldb原始碼分析--1

Leveldb原始碼分析 2012年1月21號開始研究下leveldb的程式碼,Google兩位大牛開發的單機KV儲存系統,涉及到了skip list、記憶體KV table、LRU cache管理、table檔案儲存、operation log系統等。先從邊

LevelDB原始碼分析1-基礎

1、一些約定 1.1 位元組序 Leveldb對於數字的儲存是little-endian,把int32或者int64轉換為char*的函式中,是按照先低位,後高位的順序存放,也就是litte-endian。 1.2 VarInt 把一個int32或者in

leveldb原始碼分析之sst檔案格式

轉載:http://luodw.cc/2015/10/21/leveldb-09/之前leveldb分析,講解了leveldb兩大元件memtable和log檔案。這篇文章主要分析leveldb將記憶體資料寫入磁碟檔案,這些磁碟檔案的格式,下一篇文章再分析原始碼。leveld

leveldb原始碼分析4:SkipList

skiplist思想可以具體參考這: // Create a new SkipList object that will use "cmp" for comparing keys, // and will allocate memory using "*ar

Leveldb原始碼分析--20

12 DB的開啟先分析LevelDB是如何開啟db的,萬物始於建立。在開啟流程中有幾個輔助函式:DBImpl(),DBImpl::Recover, DBImpl::DeleteObsoleteFiles, DBImpl::RecoverLogFile, DBImpl::May

LevelDB原始碼分析之六:skiplist(2)

閱讀本文可參考: LevelDB中的skiplist實現方式基本上和中的實現方式類似。它向外暴露介面非常簡單,如下: public: // Create a new SkipList object that will use "cmp" for compar

dubbo原始碼分析8 -- DubboProtocol 之提供端釋出服務export

在前面提到,在RegistryProtocol釋出服務時,首先會dubbo對外提供介面 根據url的地址,協議是dubbo,呼叫protocol.export(…), 但是根據ExtensionLoader.getExtensionLoader獲取的到的pro

LevelDB原始碼分析之一:coding

LevelDB預設使用的是小端位元組序儲存,低位位元組排放在記憶體的低地址端,高位位元組排放在記憶體的高地址端。  編碼分為變長的EncodeVarint和固定大小的EncodeFixed兩種,每種又分32位和64位。一.EncodeFixedvoid EncodeFixe

ffdshow 原始碼分析 8: 視訊解碼器類(TvideoCodecDec)

=====================================================ffdshow原始碼分析系列文章列表:=====================================================前面兩篇文章介紹了ffds

RTMPdump(libRTMP) 原始碼分析 8: 傳送訊息(Message)

=====================================================RTMPdump(libRTMP) 原始碼分析系列文章:=====================================================函式呼叫

lucene原始碼分析---8

lucene原始碼分析—查詢過程 本章開始介紹lucene的查詢過程,即IndexSearcher的search函式, IndexSearcher::search public TopDocs search(Query query, int n)

leveldb原始碼分析——leveldb層次結構

這篇文章介紹了單個sstable的物理結構以及從 如何高效的從sstable中查詢key-value這個思路理解sstable設計思想。那麼當sstable檔案數量一旦多了,又如何高效查詢呢?總不可能遍歷所有sstable檔案吧,所以leveldb 又設計一個層

二,leveldb原始碼分析(status)

// Copyright (c) 2011 The LevelDB Authors. All rights reserved.// Use of this source code is governed by a BS

Leveldb原始碼分析--9

6 SSTable之3 6.5 讀取sstable檔案 6.5.1 類層次 Sstable檔案的讀取邏輯在類Table中,其中涉及到的類還是比較多的,如圖6.5-1所示。 圖6.5-1 Table類匯出的函式只有3個,先從這三個匯出函式開始分析。其中涉