Leveldb源码中BloomFilter模块是如何实现的

来源:图像处理网作者:高宇头衔:草根站长
导读:本期聚焦于小伙伴创作的《Leveldb源码中BloomFilter模块是如何实现的》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《Leveldb源码中BloomFilter模块是如何实现的》有用,将其分享出去将是对创作者最好的鼓励。

Leveldb作为轻量级的持久化KV存储引擎,为了降低读取数据时的磁盘IO开销,在SSTable的读取流程中引入了BloomFilter机制。BloomFilter可以在不读取完整数据块的情况下,快速判断某个key是否可能存在于当前SSTable中,避免无效的磁盘读取操作。

Leveldb源码中BloomFilter模块是如何实现的

Leveldb BloomFilter模块的整体设计

Leveldb的BloomFilter实现位于util/bloom.ccutil/bloom.h文件中,核心类是BloomFilterPolicy,该类继承自FilterPolicy接口。整个模块的设计目标是用最少的内存实现尽可能高的过滤准确率,同时保证计算效率。

Leveldb的BloomFilter采用位图作为底层存储结构,每个SSTable的元数据部分会存储对应BloomFilter的位图数据。当读取SSTable时,会先加载BloomFilter位图,对目标key进行多次哈希计算后比对位图,只有所有哈希位都为1时才认为key可能存在,否则直接判定key不存在。

核心实现细节

位图存储结构

BloomFilter的位图被存储在一个字节数组中,每个字节的8个bit对应8个位置。Leveldb中BloomFilter的位数由用户配置的每key占用的bit数(bits_per_key)和当前SSTable中key的数量共同决定,计算公式如下:

总位数 = key数量 * bits_per_key

为了避免位数过少导致误判率过高,Leveldb会保证总位数至少为64位。位图的长度(字节数)则是总位数除以8向上取整。

哈希函数选择

Leveldb没有直接使用多个独立的哈希函数,而是采用了一个优化方案:先对key计算一个基础的哈希值,然后通过线性变换生成多个不同的哈希值,减少哈希计算的开销。基础哈希函数使用的是Leveldb内置的Hash函数,该函数是基于Murmur哈希的简化实现,计算速度快且分布均匀。

生成第i个哈希值的公式为:

hash_i = base_hash + i * (base_hash >> 17) + i * i

这个公式可以保证生成的多个哈希值分布相对独立,同时计算成本很低。

创建BloomFilter的实现

创建BloomFilter时,需要先把所有key的对应哈希位设置为1。下面是Leveldb中创建BloomFilter的核心代码:

#include <vector>
#include <string>
#include "util/hash.h"

// 假设bits_per_key是用户配置的每key占用bit数,k是key列表
std::string CreateBloomFilter(const std::vector<std::string>& keys, int bits_per_key) {
    // 计算需要的哈希函数个数,公式为k = bits_per_key * ln2,Leveldb中近似为 bits_per_key * 0.69,取整后最少为1
    int k = static_cast<int>(bits_per_key * 0.69);
    if (k < 1) k = 1;
    if (k > 30) k = 30; // 限制最多30个哈希函数

    // 计算总位数和字节长度
    int bits = keys.size() * bits_per_key;
    if (bits < 64) bits = 64; // 最少64位
    int bytes = (bits + 7) / 8;
    bits = bytes * 8;

    std::string filter(bytes, 0); // 初始化全0的位图

    // 遍历所有key,设置对应的哈希位
    for (const auto& key : keys) {
        // 计算基础哈希值
        uint32_t h = leveldb::Hash(key.data(), key.size(), 0xbc9f1d34);
        // 生成k个哈希值,对应设置位图的bit
        for (int i = 0; i < k; i++) {
            uint32_t bit_pos = (h + i * (h >> 17) + i * i) % bits;
            filter[bit_pos / 8] |= (1 << (bit_pos % 8));
        }
    }

    // 在filter末尾追加哈希函数个数,方便后续读取时解析
    filter.push_back(static_cast<char>(k));
    return filter;
}

判断key是否存在的实现

判断key是否存在时,需要先解析BloomFilter末尾存储的哈希函数个数k,然后对key进行同样k次哈希计算,检查所有对应位是否都为1。下面是判断逻辑的核心代码:

#include <string>
#include "util/hash.h"

bool KeyMayMatch(const std::string& key, const std::string& filter) {
    int len = filter.size();
    if (len < 2) return false; // 最少1字节位图+1字节k值

    int k = static_cast<int>(filter[len - 1]); // 取出哈希函数个数
    if (k > 30 || k < 1) return true; // 无效的k值默认认为可能存在

    int bits = (len - 1) * 8; // 位图的总位数,减去末尾存储k的1字节
    uint32_t h = leveldb::Hash(key.data(), key.size(), 0xbc9f1d34);

    for (int i = 0; i < k; i++) {
        uint32_t bit_pos = (h + i * (h >> 17) + i * i) % bits;
        if ((filter[bit_pos / 8] & (1 << (bit_pos % 8))) == 0) {
            return false; // 存在任意一位为0,直接判定不存在
        }
    }
    return true; // 所有位都为1,认为可能存在
}

参数配置与误判率

Leveldb的BloomFilter误判率和bits_per_key参数直接相关,bits_per_key越大,误判率越低,但是占用的内存也会越多。根据BloomFilter的理论公式,当哈希函数个数k = bits_per_key * ln2时,误判率最低,Leveldb的参数计算也遵循这个规律。

下面是不同bits_per_key对应的近似误判率:

bits_per_key近似误判率
5约20%
10约5%
15约1%
20约0.3%

用户在使用Leveldb时,可以通过Options::filter_policy参数配置BloomFilter,例如设置每key占用10个bit:

#include "leveldb/options.h"
#include "util/bloom.h"

leveldb::Options options;
options.filter_policy = leveldb::NewBloomFilterPolicy(10); // 每key10bit
// 打开数据库时使用该options即可

实现注意事项

  • BloomFilter只能判断key不存在的情况一定准确,判断存在的情况可能存在误判,不能用于精确匹配。
  • Leveldb的BloomFilter是针对每个SSTable单独创建的,SSTable合并时对应的BloomFilter也会被重新生成。
  • 哈希函数的个数被限制在1到30之间,避免过多的哈希计算带来性能开销。
  • 位图的总位数最少为64位,即使key数量很少也会保证基础的大小,避免误判率过高。

整体来看,Leveldb的BloomFilter实现非常轻量,在保证过滤效果的同时尽可能降低了计算和存储开销,是KV存储中优化读取性能的经典设计。理解这部分源码也有助于我们在其他场景中合理设计和使用BloomFilter数据结构。

LeveldbBloomFilter源码分析哈希函数修改时间:2026-06-18 12:55:02

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。