Bloom Filter

bloom filter是leveldb中用来在一个block中检测key是否存在的工具,主要在BloomFilterPolicy类中实现:

class BloomFilterPolicy : public FilterPolicy
{
  private:
    size_t bits_per_key_;
    size_t k_;

  public:
    explicit BloomFilterPolicy(int bits_per_key)
        : bits_per_key_(bits_per_key)
    {
        // ...
    }

    // ...

    virtual void CreateFilter(const Slice *keys, int n, std::string *dst) const
    {
        // ...
    }

    virtual bool KeyMayMatch(const Slice &key, const Slice &bloom_filter) const
    {
        // ...
    }
};

其中,bits_per_key_表示m/n,即bloom filter中包含的位数组长度与sstable中key的数量的比值。k_为hash函数的个数。

接下来看BloomFilterPolicy的构造函数,因为bloom filter的FPR(false positive rate)为(1-e^(-kn/m))^k,当k为log(2)(m/n)时,FPR最小,因此这里用bits_per_key_乘以0.69来求得k_的值,当然最后还是要将k限定在1-30的范围内:

        // We intentionally round down to reduce probing cost a little bit
        k_ = static_cast<size_t>(bits_per_key * 0.69); // 0.69 =~ ln(2)
        if (k_ < 1)
            k_ = 1;
        if (k_ > 30)
            k_ = 30;

通过调用CreateFilter函数创建一个新的bloom filter。这个函数首先计算出需要多少字节来存储这个新的bloom filter:

        // Compute bloom filter size (in both bits and bytes)
        size_t bits = n * bits_per_key_;

        // For small n, we can see a very high false positive rate.  Fix it
        // by enforcing a minimum bloom filter length.
        if (bits < 64)
            bits = 64;

        size_t bytes = (bits + 7) / 8;
        bits = bytes * 8;

接下来函数将存储bloom filter的字符串dst进行扩展,使其能够存储刚刚计算出的新增的bytes个字节,dst中原来存储的是之前得到的bloom filter,同时将当前正在构建的这个bloom filter使用的hash函数的个数存入dst的末尾:

        const size_t init_size = dst->size();
        dst->resize(init_size + bytes, 0);
        dst->push_back(static_cast<char>(k_)); // Remember # of probes in filter

最后依次取出key,通过double hash来计算出key的k_个hash后的结果,并将值存入dst指向的字符串中:

        char *array = &(*dst)[init_size];
        for (int i = 0; i < n; i++)
        {
            // Use double-hashing to generate a sequence of hash values.
            // See analysis in [Kirsch,Mitzenmacher 2006].
            uint32_t h = BloomHash(keys[i]);
            const uint32_t delta = (h >> 17) | (h << 15); // Rotate right 17 bits
            for (size_t j = 0; j < k_; j++)
            {
                const uint32_t bitpos = h % bits;
                array[bitpos / 8] |= (1 << (bitpos % 8));
                h += delta;
            }
        }

这里double hash的算法为:

Hi = h1(x) + ih2(x)
h2(x) = (h1(x) >> 17) | (h1(x) << 15)

通过调用KeyMayMatch函数可以判断一个key是不是在bloom filter表示的key集合中。这个函数首先得到给定bloom filter的一些相关信息,比如bit数、hash函数的个数(生成一个bloom filter时,hash函数的个数被编码在bloom filter的最后一个字节)等,如果hash函数的个数大于30,则直接返回true:

        const size_t len = bloom_filter.size();
        if (len < 2)
            return false;

        const char *array = bloom_filter.data();
        const size_t bits = (len - 1) * 8;

        // Use the encoded k so that we can read filters generated by
        // bloom filters created using different parameters.
        const size_t k = array[len - 1];
        if (k > 30)
        {
            // Reserved for potentially new encodings for short bloom filters.
            // Consider it a match.
            return true;
        }

然后通过依次计算key的k个hash函数的值,判断这个key是否在bloom filter表示的key集合中:

        uint32_t h = BloomHash(key);
        const uint32_t delta = (h >> 17) | (h << 15); // Rotate right 17 bits
        for (size_t j = 0; j < k; j++)
        {
            const uint32_t bitpos = h % bits;
            if ((array[bitpos / 8] & (1 << (bitpos % 8))) == 0)
                return false;
            h += delta;
        }
        return true;

LevelDB还封装了FilterBlockBuilder类来构建在sstable中存储bloom filter的block:

// A FilterBlockBuilder is used to construct all of the filters for a
// particular Table.  It generates a single string which is stored as
// a special block in the Table.
//
// The sequence of calls to FilterBlockBuilder must match the regexp:
//      (StartBlock AddKey*)* Finish
class FilterBlockBuilder
{
  public:
    explicit FilterBlockBuilder(const FilterPolicy *);

    void StartBlock(uint64_t block_offset);
    void AddKey(const Slice &key);
    Slice Finish();

  private:
    void GenerateFilter();

    const FilterPolicy *policy_;
    std::string keys_;            // Flattened key contents
    std::vector<size_t> start_;   // Starting index in keys_ of each key
    std::string result_;          // Filter data computed so far
    std::vector<Slice> tmp_keys_; // policy_->CreateFilter() argument
    std::vector<uint32_t> filter_offsets_;

    // ...
};

keys_用来存储需要添加进bloom filter的key,每个key都简单的连接在keys_的后面,start_存储每个key在keys_中的偏移量,result_为当前已经计算得到的bloom filter的值,tmp_keys_用于在调用CreateFilter函数时传递参数,filter_offsets_用于存储构建的每一个bloom filter在当前filter block中的偏移量。

StartBlock函数在开始构建bloom filter前调用。因为bloom filter的构建策略是每2k个字节就构建一个bloom filter,所以StartBlock函数会首先判断当前sstable中尚未构建bloom filter的数据是否已经达到了2k,如果达到,就调用GenerateFilter()函数为sstable中尚未构建bloom filter的数据构建新的bloom filter。

void FilterBlockBuilder::StartBlock(uint64_t block_offset)
{
    uint64_t filter_index = (block_offset / kFilterBase);
    assert(filter_index >= filter_offsets_.size());
    while (filter_index > filter_offsets_.size())
    {
        GenerateFilter();
    }
}

其中GenerateFilter函数为:

void FilterBlockBuilder::GenerateFilter()

函数首先计算出需要添加进bloom filter中的key的数量,并将key存入tmp_keys_以便传参:

    const size_t num_keys = start_.size();
    if (num_keys == 0)
    {
        // Fast path if there are no keys for this filter
        filter_offsets_.push_back(result_.size());
        return;
    }

    // Make list of keys from flattened key structure
    start_.push_back(keys_.size()); // Simplify length computation
    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);
    }

接下来将新的bloom filter在filter block中的偏移量存入filter_offsets_,然后调用BloomFilterPolicy类的CreateFilter函数构建新的bloom filter:

    // Generate filter for current set of keys and append to result_.
    filter_offsets_.push_back(result_.size());
    policy_->CreateFilter(&tmp_keys_[0], static_cast<int>(num_keys), &result_);

AddKey函数比较简单,只是将key加在keys_的后面,然后在start_中添加这个key的偏移量:

void FilterBlockBuilder::AddKey(const Slice &key)
{
    Slice k = key;
    start_.push_back(keys_.size());
    keys_.append(k.data(), k.size());
}

Finish函数完成一个filter block的构建

Slice FilterBlockBuilder::Finish()

首先,当FilterBlockBuilder中还有key尚未构建bloom filter时,调用GenerateFilter函数将其构建为一个bloom filter:

    if (!start_.empty())
    {
        GenerateFilter();
    }

然后将每个bloom filter在result_中的偏移量编码后加入result_:

    // Append array of per-filter offsets
    const uint32_t array_offset = result_.size();
    for (size_t i = 0; i < filter_offsets_.size(); i++)
    {
        PutFixed32(&result_, filter_offsets_[i]);
    }

最后将描述bloom filter在result_中的偏移量的数组在result_中的偏移量编码后存入result_,这里还要存储kFilterBaseLg(sstable中每1 << kFilterBaseLg个字节构建一个bloom filter):

    PutFixed32(&result_, array_offset);
    result_.push_back(kFilterBaseLg); // Save encoding parameter in result
    return Slice(result_);

LevelDB同时还封装了一个FilterBlockReader类用于filter block的读取:

class FilterBlockReader
{
  public:
    // REQUIRES: "contents" and *policy must stay live while *this is live.
    FilterBlockReader(const FilterPolicy *policy, const Slice &contents);
    bool KeyMayMatch(uint64_t block_offset, const Slice &key);

  private:
    const FilterPolicy *policy_;
    const char *data_;   // Pointer to filter data (at block-start)
    const char *offset_; // Pointer to beginning of offset array (at block-end)
    size_t num_;         // Number of entries in offset array
    size_t base_lg_;     // Encoding parameter (see kFilterBaseLg in .cc file)
};

其中data_指向bloom filter的起始位置,offset_指向描述bloom filter在filter block中的偏移量的数组的起始位置,num_表示offset_数组的元素个数,base_lg_即为之前提到的kFilterBaseLg。

这里调用KeyMayMatch函数来实现对给定key是否在给定block中的检查:

bool FilterBlockReader::KeyMayMatch(uint64_t block_offset, const Slice &key)
{

}

函数首先根据给定block的block_offset找到该block对应的是filter block中的第几个bloom filter:

    uint64_t index = block_offset >> base_lg_;

然后得到对应的bloom filter的起始位置和结束位置,并调用BloomFilterPolicy类的KeyMayMatch函数进行检查:

    if (index < num_)
    {
        uint32_t start = DecodeFixed32(offset_ + index * 4);
        uint32_t limit = DecodeFixed32(offset_ + index * 4 + 4);
        if (start <= limit && limit <= static_cast<size_t>(offset_ - data_))
        {
            Slice filter = Slice(data_ + start, limit - start);
            return policy_->KeyMayMatch(key, filter);
        }
        else if (start == limit)
        {
            // Empty filters do not match any keys
            return false;
        }
    }
    return true; // Errors are treated as potential matches

238 Love u

LevelDB源码分析-Bloom Filter的更多相关文章

  1. leveldb源码分析--SSTable之block

    在SSTable中主要存储数据的地方是data block,block_builder就是这个专门进行block的组织的地方,我们来详细看看其中的内容,其主要有Add,Finish和CurrentSi ...

  2. leveldb源码分析--WriteBatch

    从[leveldb源码分析--插入删除流程]和WriteBatch其名我们就很轻易的知道,这个是leveldb内部的一个批量写的结构,在leveldb为了提高插入和删除的效率,在其插入过程中都采用了批 ...

  3. leveldb源码分析--Key结构

    [注]本文参考了sparkliang的专栏的Leveldb源码分析--3并进行了一定的重组和排版 经过上一篇文章的分析我们队leveldb的插入流程有了一定的认识,而该文设计最多的又是Batch的概念 ...

  4. Leveldb源码分析--1

    coming from http://blog.csdn.net/sparkliang/article/details/8567602 [前言:看了一点oceanbase,没有意志力继续坚持下去了,暂 ...

  5. leveldb源码分析--日志

    我们知道在一个数据库系统中为了保证数据的可靠性,我们都会记录对系统的操作日志.日志的功能就是用来在系统down掉的时候对数据进行恢复,所以日志系统对一个要求可靠性的存储系统是极其重要的.接下来我们分析 ...

  6. leveldb源码分析之Slice

    转自:http://luodw.cc/2015/10/15/leveldb-02/ leveldb和redis这样的优秀开源框架都没有使用C++自带的字符串string,redis自己写了个sds,l ...

  7. LevelDB源码分析-TableBuilder生成sstable

    TableBuilder生成sstable(include/table_builder.h table/table_builder.cc) LevelDB使用TableBuilder来构建sstabl ...

  8. LevelDB源码分析--Cache及Get查找流程

    本打算接下来分析version相关的概念,但是在准备的过程中看到了VersionSet的table_cache_这个变量才想起还有这样一个模块尚未分析,经过权衡觉得leveldb的version相对C ...

  9. leveldb源码分析--SSTable之TableBuilder

    上一篇文章讲述了SSTable的格式以后,本文结合源码解析SSTable是如何生成的. void TableBuilder::Add(const Slice& key, const Slice ...

随机推荐

  1. python 基础之python的六大标准数据类型

    一:Number 数字类型(int  float  bool  complex) 1.整型: (正整数 0 负整数)#(1)二进制用0b表示intvar = 0b1010print(intvar)pr ...

  2. windows的github教程

    如何向github提交变更1. 在github上面创建project,获得git地址:2. 来到windows,首先安装git:3. 在目标目录下:git clone https://github.c ...

  3. 剑指offer 6.查找和排序 旋转数组的最小数字

    题目描述 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转. 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素. 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋 ...

  4. 【转】linux scp远程拷贝文件及文件夹

    转自:http://www.jb51.net/LINUXjishu/73131.html 1.拷贝本机/home/administrator/test整个目录至远程主机192.168.1.100的/r ...

  5. 安装lnmp1.5,搬迁Laravel项目到服务器笔记

    近期有个Laravel开发项目,需要搬到CentOS服务器做测试. 先说下项目的配置: Laravel版本5.5 --确定了php7.0以上: CentOS 7.0或以上. lnmp 1.5版 以下是 ...

  6. tp框架增删改

    选择一张表: 首先要创建模型: 1 $n = M("account"); 数据库添加数据: 1.使用数组: 1 2 3 1.使用数组 $arr = array("uid& ...

  7. CF963D Frequency of String

    https://codeforces.com/problemset/problem/123/D 题目大意 给一个字符串 \(s\),每次询问一个字符串 \(m_i\) 和一个正整数 \(k_i\),问 ...

  8. rocketmq (一)运行原理以及使用问题

    使用消息中间件可以解决高并发,那是因为消息中间件可以将消息缓存到队列之中. 但是 当消息 过多的时候,几万,几十万...消息中间件也可能会宕机,所以我们可以对消息中间件进行集群,在之前的activem ...

  9. Windows下安装Python虚拟环境

    Windows下安装Python虚拟环境 虚拟环境安装 需求概要 "虚拟环境"是从电脑独立开辟出来的环境.就好比我们生活中的橱柜中,会把酱油放在一个瓶子里,把醋放在另外一个瓶子里, ...

  10. AX2012 ERP 维度相关表数据关系图

    AX2012比AX2009可怜的几个维度来说,太丰富了,可以无数个啊.维度多了,如何使用以及管理是个问题.这个需要在做调研时,充分分析讨论确定.以下为维度表关联关系,在做SQL取值时需要了解下,比如在 ...