Leveldb源碼分析--13

8 FilterPolicy&Bloom之2

8.5 構建FilterBlock

8.5.1 FilterBlockBuilder

瞭解了filter機制,現在來看看filter block的構建,這就是類FilterBlockBuilder。它爲指定的table構建所有的filter,結果是一個string字符串,並作爲一個block存放在table中。它有三個函數接口:

  // 開始構建新的filter block,TableBuilder在構造函數和Flush中調用
  void StartBlock(uint64_tblock_offset);
  // 添加key,TableBuilder每次向data block中加入key時調用
  void AddKey(const Slice&key);
  // 結束構建,TableBuilder在結束對table的構建時調用
  Slice Finish();  
FilterBlockBuilder的構建順序必須滿足如下範式:(StartBlock AddKey*)* Finish,顯然這和前面講過的BlockBuilder有所不同。
其成員變量有:
  const FilterPolicy* policy_; // filter類型,構造函數參數指定
  std::string keys_;          //Flattened key contents
  std::vector<size_t> start_;   // 各key在keys_中的位置
  std::string result_;          // 當前計算出的filter data
  std::vector<uint32_t>filter_offsets_; // 各個filter在result_中的位置
  std::vector<Slice> tmp_keys_;// policy_->CreateFilter()參數 
前面說過base是2KB,這對應兩個常量kFilterBase =11, kFilterBase =(1<<kFilterBaseLg);其實從後面的實現來看tmp_keys_完全不必作爲成員變量,直接作爲函數GenerateFilter()的棧變量就可以。下面就分別分析三個函數接口。

8.5.2 FilterBlockBuilder::StartBlock()

它根據參數block_offset計算出filter index,然後循環調用GenerateFilter生產新的Filter。

  uint64_t filter_index =(block_offset / kFilterBase);
  assert(filter_index >=filter_offsets_.size());
  while (filter_index >filter_offsets_.size()) GenerateFilter();
我們來到GenerateFilter這個函數,看看它的邏輯。

//S1 如果filter中key個數爲0,則直接壓入result_.size()並返回
  const size_t num_keys =start_.size();
  if (num_keys == 0) { // there are no keys for this filter
    filter_offsets_.push_back(result_.size()); //result_.size()應該是0
    return;
  }
//S2 從key創建臨時key list,根據key的序列字符串kyes_和各key在keys_中的開始位置start_依次提取出key。
  start_.push_back(keys_.size());  // Simplify lengthcomputation
  tmp_keys_.resize(num_keys);
  for (size_t i = 0; i <num_keys; i++) {
    const char* base =keys_.data() + start_[i]; // 開始指針
    size_t length = start_[i+1] -start_[i]; // 長度
    tmp_keys_[i] = Slice(base,length);
  }
//S3 爲當前的key集合生產filter,並append到result_
  filter_offsets_.push_back(result_.size());
  policy_->CreateFilter(&tmp_keys_[0], num_keys, &result_);
//S4 清空,重置狀態
  tmp_keys_.clear();
  keys_.clear();
  start_.clear();

8.5.3 FilterBlockBuilder::AddKey()

這個接口很簡單,就是把key添加到key_中,並在start_中記錄位置。

  Slice k = key;
  start_.push_back(keys_.size());
  keys_.append(k.data(),k.size());

8.5.4 FilterBlockBuilder::Finish()

調用這個函數說明整個table的data block已經構建完了,可以生產最終的filter block了,在TableBuilder::Finish函數中被調用,向sstable寫入meta block。
函數邏輯爲:
//S1 如果start_數字不空,把爲的key列表生產filter
  if (!start_.empty()) GenerateFilter();
//S2 從0開始順序存儲各filter的偏移值,見filter block data的數據格式。
  const uint32_t array_offset =result_.size();
  for (size_t i = 0; i < filter_offsets_.size();i++) {
    PutFixed32(&result_,filter_offsets_[i]);
  }
//S3 最後是filter個數,和shift常量(11),並返回結果
  PutFixed32(&result_,array_offset);
  result_.push_back(kFilterBaseLg);  // Save encoding parameter in result
  return Slice(result_);

8.5.5 簡單示例

讓我們根據TableBuilder對FilterBlockBuilder接口的調用範式:
(StartBlock AddKey*)* Finish以及上面的函數實現,結合一個簡單例子看看leveldb是如何爲data block創建filter block(也就是meta block)的。
考慮兩個datablock,在sstable的範圍分別是:Block 1 [0, 7KB-1], Block 2 [7KB, 14.1KB]

S1 首先TableBuilder爲Block 1調用FilterBlockBuilder::StartBlock(0),該函數直接返回;
S2 然後依次向Block 1加入k/v,其中會調用FilterBlockBuilder::AddKey,FilterBlockBuilder記錄這些key。
S3 下一次TableBuilder添加k/v時,例行檢查發現Block 1的大小超過設置,則執行Flush操作,Flush操作在寫入Block 1後,開始準備Block 2並更新block offset=7KB,最後調用FilterBlockBuilder::StartBlock(7KB),開始爲Block 2構建Filter。
S4 在FilterBlockBuilder::StartBlock(7KB)中,計算出filter index = 3,觸發3次GenerateFilter函數,爲Block 1添加的那些key列表創建filter,其中第2、3次循環創建的是空filter。
此時filter的結構如圖8.5-1所示。

圖8.5-1

在StartBlock(7KB)時會向filter的偏移數組filter_offsets_壓入兩個包含空key set的元素,filter_offsets_[1]和filter_offsets_[2],它們的值都等於7KB-1。
S5 Block 2構建結束,TableBuilder調用Finish結束table的構建,這會再次觸發Flush操作,在寫入Block 2後,爲Block 2的key創建filter。最終的filter如圖8.5-2所示。

圖8.5-2

這裏如果Block 1的範圍是[0, 1.8KB-1],Block 2從1.8KB開始,那麼Block 2將會和Block 1共用一個filter,它們的filter都被生成到filter 0中。
當然在TableBuilder構建表時,Block的大小是根據參數配置的,也是基本均勻的。

8.6 讀取FilterBlock

8.6.1 FilterBlockReader

FilterBlock的讀取操作在FilterBlockReader類中,它的主要功能是根據傳入的FilterPolicy和filter,進行key的匹配查找。
它有如下的幾個成員變量:
  const FilterPolicy* policy_; // filter策略
  const char* data_;    // filter data指針 (at block-start)
  const char* offset_;  // offset array的開始地址 (at block-end)
  size_t num_;       // offsetarray元素個數
  size_t base_lg_;    // 還記得kFilterBaseLg嗎
Filter策略和filter block內容都由構造函數傳入。一個接口函數,就是key的批判查找:
bool KeyMayMatch(uint64_t block_offset, const Slice& key);

8.6.2 構造

在構造函數中,根據存儲格式解析出偏移數組開始指針、個數等信息。

FilterBlockReader::FilterBlockReader(const FilterPolicy* policy, constSlice& contents)
    : policy_(policy),data_(NULL), offset_(NULL), num_(0), base_lg_(0) {
  size_t n = contents.size();
  if (n < 5) return;  // 1 byte forbase_lg_ and 4 for start of offset array
  base_lg_ = contents[n-1]; // 最後1byte存的是base
  uint32_t last_word =DecodeFixed32(contents.data() + n - 5); //偏移數組的位置
  if (last_word > n - 5)return;
  data_ = contents.data();
  offset_ = data_ + last_word; // 偏移數組開始指針
  num_ = (n - 5 - last_word) / 4; // 計算出filter個數

8.6.3 查找

查找函數傳入兩個參數,@block_offset是查找data block在sstable中的偏移,Filter根據此偏移計算filter的編號;@key是查找的key。
聲明如下:
bool FilterBlockReader::KeyMayMatch(uint64_t block_offset, constSlice& key)
它首先計算出filterindex,根據index解析出filter的range,如果是合法的range,就從data_中取出filter,調用policy_做key的匹配查詢。函數實現:
  uint64_t index = block_offset>> base_lg_; // 計算出filter index
  if (index < num_) {
    uint32_t start =DecodeFixed32(offset_ + index*4); // 解析出filter的range
    uint32_t limit =DecodeFixed32(offset_ + index*4 + 4);
    if (start <= limit&& limit <= (offset_ - data_)) {
      Slice filter = Slice(data_ +start, limit - start); // 根據range得到filter
      returnpolicy_->KeyMayMatch(key, filter);
    } else if (start == limit) {
      return false;  // 空filter不匹配任何key
    }
  }
  return true; // 當匹配處理
至此,FilterPolicy和Bloom就分析完了。
發佈了90 篇原創文章 · 獲贊 99 · 訪問量 164萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章