当前位置: 首页 > news >正文

leveldb-FilterBlock实现

布隆过滤器

leveldb的写入的策略采用了 LSM 树🌲,加上很多的优化,写入的性能很强悍。

但是读取的性能是稍微差了一点,可能出现 读放大 的情况,为了进行优化,避免很多的不必要的磁盘IO,使用布隆过滤器 快速判断数据是不是处于某个SSTable

常用的判断某个数据是否是处于集合中我们直接用 set Or hash就好了,但是这 必须要我们保存所有的数据,这在判断数据库的情况是肯定不行的,解决的办法就是使用 布隆过滤器

bitmap进行判断,使用K个哈希函数将key进行映射到上面,那么我们需要判断 key 是否是处于这个集合中,再次用 k 个哈希函数进行映射。

  • 如果说映射到的 bitmap 中为0, 那么 数据一定不在集合中
  • 如果说都是1的话, 可能是在集合中

如果说 n 的大小实在是太小了,那么 假阳性的情况可能是比较严重的

Filter

const FilterPolicy* policy_;    /* filter 类型,如 BloomFilterPolicy */
    std::string keys_;              /* User Keys,全部塞到一个 string 中 */
    std::vector<size_t> start_;     /* 每一个 User Key 在 keys_ 中的起始位置 */
    std::string result_;            /* keys_ 通过 policy_ 计算出来的 filtered data */
    std::vector<Slice> tmp_keys_;   /* policy_->CreateFilter() 的参数 */
    
    /* filter 在 result_ 中的位置,filter_offsets_.size() 就是 Bloom Filter 的数量 */
    std::vector<uint32_t> filter_offsets_;  
};

FilterBlock

整个的写入流程

  1. DataBlock 中获得 Key =====> AddKey(const Slice& key), 将所有的 key 的数据都放在了 keys 中,因为是按照 string 来存储的,所以用 start 标识每个 key 的位置
  2. key 都保存下来之后我们就要开始调用 StartBlock(uint64_t offset), 传入参数 offset 也就是data block 的偏移量,我们就能知道处理了多少数据,用于计算 filter 的数目(每2KB创建一个filter)
  3. 具体的调用 GenerateFilter(), 按照规格 (2KB)调用创建 布隆过滤器的函数
  4. 最终调用Finish完成整个的构建,将最终的一些 metadata 写到最终的数据中
#include "filterBlock.h"
#include "Filter.h"

#include "Coding.h"

using namespace xindb;

// DataBlock 中每 2KB 就生成一个布隆过滤器
static const size_t kFilterBasebit = 11;
static const size_t kFilterBase    = 1 << kFilterBasebit;  


FilterBlockBuilder::FilterBlockBuilder(const FilterPolicy* policy)
    :policy_(policy) {}


// 使用 ADD 将数据写入之后开始创建 filterblock, 需要传入 datablock 的偏移量来计算 filter 的数目
void FilterBlockBuilder::StartBlock(uint64_t block_offset) {
    uint64_t filter_index = (block_offset / kFilterBase);       // 需要创建的 filter 的数目
    assert(filter_index >= filter_offsets_.size());

    // filter_offset 保存每个 bloom filter 的起始偏移量
    while (filter_index > filter_offsets_.size()) {
        GenerateFilter();
    }
}


// 将 InternalKey 写入到 keys_ 中, 用 start_ 标识 key 的位置
// 将全部的数据写入完毕之后会调用StartBlock 
void FilterBlockBuilder::AddKey(const Slice& key) {
    Slice k = key;
    start_.push_back(keys_.size());        // 新插入的key的起始位置
    keys_.append(k.data(), k.size());       // 直接append到keys中去
}


// 生成Filter, 每 2KB 生成一个 bloom 
void FilterBlockBuilder::GenerateFilter() {
    // 获得key,放到tmp_keys中
    const size_t num_keys = start_.size();

    if (num_keys == 0) {
        filter_offsets_.push_back(result_.size());
        return ;
    }

    start_.push_back(keys_.size());

    tmp_keys_.resize(num_keys);                 // 作为参数传入到生成 Filter 的函数


    // 将keys中的 InternalKey 取出来
    for (size_t i = 0; i < num_keys; i++) {
        const char* base = keys_.data() + start_[i];
        size_t len = start_[i+1] - start_[i];
        tmp_keys_[i] = Slice(base, len);
    }

    // 记录 Bloom Filter 结果的偏移量
    filter_offsets_.push_back(result_.size());

    policy_->CreateFilter(&tmp_keys_[0], static_cast<int>(num_keys), &result_);

    tmp_keys_.clear();
    keys_.clear();
    start_.clear();
}


// 完成最终的 MetaData 的写入,供读取的时候能够快速的定位
Slice FilterBlockBuilder::Finish() {
    if (!start_.empty()) {
        GenerateFilter();
    }

    // 将所有的偏移量写入到 result_ 的尾部
    size_t Size = result_.size();
    for (size_t i = 0; i < result_.size(); i++) {
        PutFixed32(&result_, filter_offsets_[i]);
    }

    // 将过滤器的个数扔到尾部
    PutFixed32(&result_, Size);

    // 将Base也就是我们每2KB生成一个过滤器写入
    result_.push_back(kFilterBase);
    return Slice(result_);
}



相关文章:

  • 关于移动端H5获取微信非静默授权被拦截进入【微信快照页】问题及解决方案
  • token和JWT token区别、登录安全、页面权限、数据权限、单点登录
  • Liteos信号量的使用
  • 基于Verilog搭建一个卷积运算单元的简单实现
  • pytorch-实现mnist手写数字识别(彩色)
  • C/C++语言100题练习计划 99——找第一个只出现一次的字符
  • Go使用Gin+mysql实现增删改查
  • PIE-Engine:房山区洪涝灾害风险评价
  • 【我的渲染技术进阶之旅】如何编译Filament的windows版本程序?
  • 03 C++ 字符串、向量和数组
  • python 代码 C 执行
  • 字节外包凭借【ui自动化测试框架】成功进入内部编制
  • 用 Plop 加快项目相似代码生成
  • Codeforces Round #822 (Div. 2) 补题 (A、B、C)
  • 【初阶与进阶C++详解】第十六篇:AVL树-平衡搜索二叉树(定义+插入+旋转+验证)
  • [ JavaScript ] 数据结构与算法 —— 链表
  • [js高手之路]搞清楚面向对象,必须要理解对象在创建过程中的内存表示
  • Android Volley源码解析
  • angular2 简述
  • Go 语言编译器的 //go: 详解
  • gops —— Go 程序诊断分析工具
  • HTML-表单
  • HTTP 简介
  • Intervention/image 图片处理扩展包的安装和使用
  • JavaScript对象详解
  • Linux快速复制或删除大量小文件
  • Redis 懒删除(lazy free)简史
  • VuePress 静态网站生成
  • windows下mongoDB的环境配置
  • 第十八天-企业应用架构模式-基本模式
  • 仿天猫超市收藏抛物线动画工具库
  • 个人博客开发系列:评论功能之GitHub账号OAuth授权
  • 推荐一个React的管理后台框架
  • 国内唯一,阿里云入选全球区块链云服务报告,领先AWS、Google ...
  • 说说我为什么看好Spring Cloud Alibaba
  • ​LeetCode解法汇总518. 零钱兑换 II
  • # Panda3d 碰撞检测系统介绍
  • (13)Hive调优——动态分区导致的小文件问题
  • (2)STM32单片机上位机
  • (2009.11版)《网络管理员考试 考前冲刺预测卷及考点解析》复习重点
  • (C语言)fgets与fputs函数详解
  • (Mac上)使用Python进行matplotlib 画图时,中文显示不出来
  • (二)c52学习之旅-简单了解单片机
  • (附源码)ssm教师工作量核算统计系统 毕业设计 162307
  • (四)搭建容器云管理平台笔记—安装ETCD(不使用证书)
  • (转)Java socket中关闭IO流后,发生什么事?(以关闭输出流为例) .
  • (转)Linux NTP配置详解 (Network Time Protocol)
  • (转)项目管理杂谈-我所期望的新人
  • . NET自动找可写目录
  • .bat批处理(一):@echo off
  • .net core 连接数据库,通过数据库生成Modell
  • .NET 的程序集加载上下文
  • .Net 路由处理厉害了
  • .NET/C# 推荐一个我设计的缓存类型(适合缓存反射等耗性能的操作,附用法)
  • .NET框架