天天看點

LevelDB源碼剖析之SSTable_sstable檔案的讀取

原文:http://blog.csdn.net/sparkliang/article/details/8681759

6 SSTable之3

6.5 讀取sstable檔案

6.5.1 類層次

Sstable檔案的讀取邏輯在類Table中,其中涉及到的類還是比較多的,如圖6.5-1所示。

LevelDB源碼剖析之SSTable_sstable檔案的讀取

圖6.5-1

Table類導出的函數隻有3個,先從這三個導出函數開始分析。其中涉及到的類(包括上圖中為畫出的)都會一一遇到,然後再一一拆解。

本節分析sstable的打開邏輯,後面再分析key的查找與資料周遊。

6.5.2 Table::Open()

打開一個sstable檔案,函數聲明為:

static Status Open(const Options& options, RandomAccessFile* file, uint64_tfile_size, Table** table);

這是Table類的一個靜态函數,如果操作成功,指針*table指向新打開的表,否則傳回錯誤。

要打開的檔案和大小分别由參數file和file_size指定;option是一些選項;

下面就分析下函數邏輯:

S1 首先從檔案的結尾讀取Footer,并Decode到Footer對象中,如果檔案長度小于Footer的長度,則報錯。Footer的decode很簡單,就是根據前面的Footer結構,解析并判斷magic number是否正确,解析出meta index和index block的偏移和長度。

[cpp]  view plain copy

  1. *table = NULL;  
  2. if (size <Footer::kEncodedLength) { // 檔案太短  
  3.   returnStatus::InvalidArgument("file is too short to be an sstable");  
  4. }  
  5. charfooter_space[Footer::kEncodedLength]; // Footer大小是固定的  
  6. Slice footer_input;  
  7. Status s = file->Read(size -Footer::kEncodedLength, Footer::kEncodedLength,  
  8.                      &footer_input, footer_space);  
  9. if (!s.ok()) return s;  
  10. Footer footer;  
  11. s =footer.DecodeFrom(&footer_input);  
  12. if (!s.ok()) return s;  

S2 解析出了Footer,我們就可以讀取index block和meta index了,首先讀取index block。

[cpp]  view plain copy

  1. BlockContents contents;  
  2. Block* index_block = NULL;  
  3. if (s.ok()) {  
  4.   s = ReadBlock(file, ReadOptions(),footer.index_handle(), &contents);  
  5.   if (s.ok()) {  
  6.     index_block = newBlock(contents);  
  7.   }  
  8. }  

這是通過調用ReadBlock完成的,下面會分析這個函數。

S3 已經成功讀取了footer和index block,此時table已經可以響應請求了。建構table對象,并讀取metaindex資料建構filter policy。如果option打開了cache,還要為table建立cache。

[cpp]  view plain copy

  1. if (s.ok()) {  
  2.   // 已成功讀取footer和index block: 可以響應請求了  
  3.   Rep* rep = new Table::Rep;  
  4.   rep->options = options;  
  5.   rep->file = file;  
  6.   rep->metaindex_handle =footer.metaindex_handle();  
  7.   rep->index_block =index_block;  
  8.   rep->cache_id =(options.block_cache ? options.block_cache->NewId() : 0);  
  9.   rep->filter_data = rep->filter= NULL;  
  10.   *table = new Table(rep);  
  11.   (*table)->ReadMeta(footer);// 調用ReadMeta讀取metaindex  
  12. } else {  
  13.   if (index_block) deleteindex_block;  
  14. }  

到這裡,Table的打開操作就已經為完成了。下面來分析上面用到的ReadBlock()和ReadMeta()函數.

6.5.3 ReadBlock()

前面講過block的格式,以及Block的寫入(TableBuilder::WriteRawBlock),現在我們可以輕松的分析Block的讀取操作了。

這是一個全局函數,聲明為:

Status ReadBlock(RandomAccessFile* file, const ReadOptions& options,  const BlockHandle&handle, BlockContents* result);

下面來分析實作邏輯:

S1 初始化結果result,BlockContents是一個有3個成員的結構體。

[cpp]  view plain copy

  1. result->data = Slice();  
  2. result->cachable = false; // 無cache  
  3. result->heap_allocated =false; // 非heap配置設定  

S2 根據handle指定的偏移和大小,讀取block内容,type和crc32值,其中常量kBlockTrailerSize=5= 1byte的type和4bytes的crc32。

  Status s = file->Read(handle.offset(),handle.size() + kBlockTrailerSize, &contents, buf);

S3 如果option要校驗CRC32,則計算content + type的CRC32并校驗。

S4 最後根據type指定的存儲類型,如果是非壓縮的,則直接取資料賦給result,否則先解壓,把解壓結果賦給result,目前支援的是snappy壓縮。

另外,檔案的Read接口傳回的Slice結果,其data指針可能沒有使用我們傳入的buf,如果沒有,那麼釋放Slice的data指針就是我們的事情,否則就是檔案來管理的。

[cpp]  view plain copy

  1. if (data != buf) { // 檔案自己管理,cacheable等标記設定為false  
  2.    delete[] buf;  
  3.    result->data =Slice(data, n);  
  4.    result->heap_allocated= result->cachable =false;  
  5.  } else { // 讀取者自己管理,标記設定為true  
  6.    result->data =Slice(buf, n);  
  7.    result->heap_allocated= result->cachable = true;  
  8.  }  

對于壓縮存儲,解壓後的字元串存儲需要讀取者自行配置設定的,是以标記都是true。

6.5.4 Table::ReadMeta()

解決完了Block的讀取,接下來就是meta的讀取了。函數聲明為:

void Table::ReadMeta(const Footer& footer)

函數邏輯并不複雜,

S1首先調用ReadBlock讀取meta的内容

[cpp]  view plain copy

  1. if(rep_->options.filter_policy == NULL) return; // 不需要metadata  
  2. ReadOptions opt;  
  3. BlockContents contents;  
  4. if (!ReadBlock(rep_->file,opt, footer.metaindex_handle(), &contents).ok()) {  
  5.   return; // 失敗了也沒報錯,因為沒有meta資訊也沒關系  
  6. }  

S2 根據讀取的content建構Block,找到指定的filter;如果找到了就調用ReadFilter建構filter對象。Block的分析留在後面。

[cpp]  view plain copy

  1. Block* meta = newBlock(contents);  
  2. Iterator* iter =meta->NewIterator(BytewiseComparator());  
  3. std::string key ="filter.";  
  4. key.append(rep_->options.filter_policy->Name());  
  5. iter->Seek(key);  
  6. if (iter->Valid() &&iter->key() == Slice(key)) ReadFilter(iter->value());  
  7. delete iter;  
  8. delete meta;  

6.5.5 Table::ReadFilter()

根據指定的偏移和大小,讀取filter,函數聲明:

void ReadFilter(const Slice& filter_handle_value);

簡單分析下函數邏輯

S1 從傳入的filter_handle_value Decode出BlockHandle,這是filter的偏移和大小;

  BlockHandle filter_handle;

  filter_handle.DecodeFrom(&filter_handle_value);

S2 根據解析出的位置讀取filter内容,ReadBlock。如果block的heap_allocated為true,表明需要自行釋放記憶體,是以要把指針儲存在filter_data中。最後根據讀取的data建立FilterBlockReader對象。

[cpp]  view plain copy

  1. ReadOptions opt;  
  2. BlockContents block;  
  3. ReadBlock(rep_->file, opt,filter_handle, &block);  
  4. if (block.heap_allocated)rep_->filter_data = block.data.data(); // 需要自行釋放記憶體  
  5. rep_->filter = newFilterBlockReader(rep_->options.filter_policy, block.d