leveldb原始碼分析(2)-bloom filter

Jefffrey發表於2019-05-09

bloom filter in leveldb

引言

bloom filter是一種用於快速判斷某個元素是否屬於集合的多雜湊對映查詢演算法,但是並不要求100%正確。典型的應用場合有垃圾郵件過濾,http快取中判斷一條url是否在現有的url集合中等。

本文著重於分析bloom filter在leveldb中的實現和應用,不會涉及太多關於bloom filter本身的原理,效能分析。

原始碼分析

bloom filter的實現涉及到3個檔案,include/leveldb/filter_policy.h,util/filter_policy.cc,util/bloom.cc其中BloomFilterPolicyFilterPolicy的派生類,所以我們先來看`filter_policy的實現。

class FilterPolicy {
 public:
  virtual ~FilterPolicy();//解構函式
  virtual const char* Name() const = 0;//返回該policy的名字,若編碼改變,則名字必須改變
  virtual void CreateFilter(const Slice* keys, int n, std::string* dst)
      const = 0;
//keys包含了一串key,(可能重複),按照使用者定製的比較器排序
//該函式根據這串key建立一個過濾器dst,(字串形式)
  virtual bool KeyMayMatch(const Slice& key, const Slice& filter) const = 0;
//判斷key是否包含在filter中,大部分情況下應該返回false,即隨機一個key,在filter中命中的概率很小。
};
extern const FilterPolicy* NewBloomFilterPolicy(int bits_per_key);
//使用bloom filter構建一個filterPolicy,使用者必須在某個資料庫使用完該結果關閉後刪除該結果。若你使用的比較器忽略了key的某些部分,那麼你必須自行實現一個FilterPolicy

上述的FilterPolicy只是一個基類介面,我們可以按照該介面實現自己的filter policy。下面的BloomFilter類才是具體的實現。

private:
  size_t bits_per_key_;//每個key所佔的bit(粗略),將按照該值分配filter大小
  size_t k_;//每個key雜湊到陣列中的bit數,略小於bits_per_key_
public:
  explicit BloomFilterPolicy(int bits_per_key)
      : bits_per_key_(bits_per_key) {
    k_ = static_cast<size_t>(bits_per_key * 0.69);  // 0.69 =~ ln(2)
    if (k_ < 1) k_ = 1;
    if (k_ > 30) k_ = 30;
  }
virtual const char* Name() const {
    return "leveldb.BuiltinBloomFilter2";
  }



  virtual void CreateFilter(const Slice* keys, int n, std::string* dst) const {
    //計算filter的大小,n為key的數量
    size_t bits = n * bits_per_key_;

    // 限制最小值
    if (bits < 64) bits = 64;
    //計算所需bite大小
    size_t bytes = (bits + 7) / 8;
    bits = bytes * 8;

    const size_t init_size = dst->size();
    dst->resize(init_size + bytes, 0);
    dst->push_back(static_cast<char>(k_));
    char* array = &(*dst)[init_size];
    //先偏移到init_size的位置再定址,[]的運算優先順序比&大
    for (size_t i = 0; i < n; i++) {
      //利用hash演算法生成一系列hash值,32bit
      uint32_t h = BloomHash(keys[i]);
      const uint32_t delta = (h >> 17) | (h << 15);  // 向右旋轉17個bit,該實現非常精髓地利用了位運算,你們自己感受一下-_-
      for (size_t j = 0; j < k_; j++) {
        const uint32_t bitpos = h % bits;
        array[bitpos/8] |= (1 << (bitpos % 8));
        h += delta;
      }
      //由於用的是char陣列而不是bit陣列,所以賦值到相應的bit有些麻煩,需要先定址到相應byte然後做位運算。
      //最後的h+=delta以及為什麼需要向右旋轉17bit跟hash演算法的實現有關,詳情請搜尋double hashing
    }
  }

KeyMayMatch的實現與CreateFilter的實現類似,給定一個key和一個filter判斷key是否在filter中,不再贅述。

double hashing的公式為

$$
h(i,k)= (h_1(k)+i*h_2(k)) mod |T|
$$

可以把BloomHash當做h1,向右旋轉17個bit當做h2。

k_值的選擇是有依據的,若k_完全等於bit_per_key_,那麼filter會非常擁擠,充滿了1,我們知道filter中1的數量越少 越好,否則隨機一個key也能命中filter,但k_太小的話,一個隨機key通過hash後只需命中k_(少量)個位置即可命中。有興趣的讀者可以自行查閱文獻,以下文獻詳細證明了k的選擇在什麼時候是誤差最小的:bloom filter in math

總結

bloom filter的實現比較簡單,沒有多少複雜的邏輯和技巧,對於引數的選擇有興趣的讀者可以自行試驗或者參考相關文獻。

to be continued

@SeeYouSpaceCowboy

相關文章