【Redis源码】bloomfilter布隆过滤器

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 【Redis源码】bloomfilter布隆过滤器

前言:

布隆过滤器有很多使用场景比如说反垃圾邮件,从数十亿的垃圾邮件列表中判断某邮件是否为垃圾。或者说解决缓存击穿问题。或者在一些去重场景中都可以使用到,例如推送新闻不重复。

布隆过滤器是redis的一个插件功能,redis4.0之后提供了插件功能才正式登场。

bloomfilter版本:v1.1.1

参考资料:

redis modulesAPI:

http://www.redis.cn/topics/modules-api-ref.html

Bloom_filter:

https://en.wikipedia.org/wiki/Bloom_filter

Byte pair encoding :

https://en.wikipedia.org/wiki/Byte_pair_encoding

(一)安装布隆过滤器插件

下载

#wget https://github.com/RedisLabsModules/rebloom/archive/v1.1.1.tar.gz

解压并且编译

#tar -zxvf v1.1.1.tar.gz
#cd RedisBloom-1.1.1/
#make

配置redis.conf

loadmodule /Users/edz/Desktop/src-source/RedisBloom-1.1.1/rebloom.so

启动

#redis-server redis.conf

(二)命令解析

//布隆过滤器命令
#指定错误率
BF.RESERVE  

#添加布隆过滤
BF.ADD  

#添加多个值到过滤器中
BF.MADD   ...

#该命令将向bloom过滤器添加一个或多个项,如果它还不存在,则默认情况下创建它。
BF.INSERT  [CAPACITY {cap}] [ERROR {ERROR}] [NOCREATE] ITEMS {item…}

#判断值是否存在
BF.EXISTS  

#判断多个值是否存在
BF.MEXISTS   ..

#查看键值相关信息
BF.DEBUG  

#布隆过滤持久化操作
BF.SCANDUMP  
BF.LOADCHUNK  

// 布谷鸟过滤器命令
#指定布谷鸟过滤器错误率
CF.RESERVE    
#添加布谷鸟过滤
CF.ADD  

#该命令将布谷鸟过滤器添加一个或多个项,如果它还不存在,则默认情况下创建它。
CF.INSERT  [CAPACITY {cap}] [ERROR {ERROR}] [NOCREATE] ITEMS {item…}

#判断布谷鸟过滤器值是否存在
CF.EXISTS  

#判断布谷鸟过滤器多个值是否存在
CF.MEXISTS   ..

#统计布谷鸟过滤器某个键值出现次数
CF.COUNT  

#删除布谷鸟过滤器某个键值
CF.DEL  

#布谷鸟过滤器持久化
CF.SCANDUMP  
CF.LOADCHUNK  

#查看布谷鸟过滤器键值相关信息
CF.DEBUG  

布谷鸟过滤器与布隆过滤器相比,布谷鸟过滤器占用资源相对来说少一些。并且精准度会更高一些,还支持删除命令。和统计命令。

(三) 如何编写一个modules

3.1 如下是一个简单的例子:

#include"redismodule.h"
#include

intHelloworld_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
   RedisModule_ReplyWithLongLong(ctx,1);
   return REDISMODULE_OK;
}

intRedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
   if (RedisModule_Init(ctx,"helloworld",1,REDISMODULE_APIVER_1)
       == REDISMODULE_ERR) return REDISMODULE_ERR;

   if (RedisModule_CreateCommand(ctx,"helloworld.get",
       Helloworld_RedisCommand) == REDISMODULE_ERR)
       return REDISMODULE_ERR;

   return REDISMODULE_OK;
}

RedisModule_OnLoad在每个插件中都会存在,它是要初始化模块、注册其命令以及可能使用的其他私有数据结构的入口点。

RedisModule_OnLoad函数中如小例子,我们会调用两个函数:

RedisModule_Init 和RedisModule_CreateCommand,RedisModule_Init用于初始化Module信息。

而我们Helloworld_RedisCommand函数,是通过RedisModule_CreateCommand注册的一个命令。

(四)原理解析

4.1什么是布隆过滤器

布隆过滤器可以理解成一个不精准的set结构,你用它可以判断某个值是否存在。但是由于布隆过滤器也不是特别精准,不过可以通过参数设置。只要参数合理,它的精准度相对来说足够精准,只会有小小的误判。

一个布隆过滤器是一串被指为0的数组。如果要知道某个值是否存在。通过hash获得这个值的位置,这个值的位置为1则是存在。

4.2 命令注册

intRedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
   if (RedisModule_Init(ctx, "bf", REBLOOM_MODULE_VERSION, REDISMODULE_APIVER_1) !=
       REDISMODULE_OK) {
       return REDISMODULE_ERR;
   }
   //。。。省略
#define CREATE_CMD(name, tgt, attr)     //创建命令宏                                                            \
   do {                                                                                           \
       if (RedisModule_CreateCommand(ctx, name, tgt, attr, 1, 1, 1) != REDISMODULE_OK) {          \
           return REDISMODULE_ERR;                                                                \
       }                                                                                          \
   } while (0)
#define CREATE_WRCMD(name, tgt) CREATE_CMD(name, tgt, "write deny-oom")
#define CREATE_ROCMD(name, tgt) CREATE_CMD(name, tgt, "readonly fast")  

   CREATE_WRCMD("BF.RESERVE", BFReserve_RedisCommand);
   CREATE_WRCMD("BF.ADD", BFAdd_RedisCommand);
   CREATE_WRCMD("BF.MADD", BFAdd_RedisCommand);
   CREATE_WRCMD("BF.INSERT", BFInsert_RedisCommand);
   CREATE_ROCMD("BF.EXISTS", BFCheck_RedisCommand);
   CREATE_ROCMD("BF.MEXISTS", BFCheck_RedisCommand);

   // Bloom - Debug
   CREATE_ROCMD("BF.DEBUG", BFInfo_RedisCommand);
   // Bloom - AOF
   CREATE_ROCMD("BF.SCANDUMP", BFScanDump_RedisCommand);
   CREATE_WRCMD("BF.LOADCHUNK", BFLoadChunk_RedisCommand);

   // Cuckoo Filter commands
   CREATE_WRCMD("CF.RESERVE", CFReserve_RedisCommand);
   CREATE_WRCMD("CF.ADD", CFAdd_RedisCommand);
   CREATE_WRCMD("CF.ADDNX", CFAdd_RedisCommand);
   CREATE_WRCMD("CF.INSERT", CFInsert_RedisCommand);
   CREATE_WRCMD("CF.INSERTNX", CFInsert_RedisCommand);
   CREATE_ROCMD("CF.EXISTS", CFCheck_RedisCommand);
   CREATE_ROCMD("CF.MEXISTS", CFCheck_RedisCommand);
   CREATE_ROCMD("CF.COUNT", CFCheck_RedisCommand);

   // Technically a write command, but doesn't change memory profile
   CREATE_CMD("CF.DEL", CFDel_RedisCommand, "write fast"); //谢

   // AOF:
   CREATE_ROCMD("CF.SCANDUMP", CFScanDump_RedisCommand);
   CREATE_WRCMD("CF.LOADCHUNK", CFLoadChunk_RedisCommand);

   CREATE_ROCMD("CF.DEBUG", CFInfo_RedisCommand);
   //...省略
   return REDISMODULE_OK;
}

代码中我们可以看到注册命令时,携带了一些内容如"readonly fast",这个是创建命令的信号量, 限定了命令的操作,用空格隔开的C 字符串组成。信号量如下:

“write”: 该命令会修改数据集

“readonly”: 该命令仅返回值,不做修改

“admin”: 管理命令(可以调整复制或类似操作)

“deny-oom”: 该命令使用额外内存,在内存超用的场景下应当被拒绝

“deny-script”: 禁止使用Lua 脚本

“allow-loading”: 允许Redis 服务器加载数据时执行该命令,只有不和数据集交互的命令在该模式下被允许执行。否则不设置该信号量。

“pubsub”: 该命令回想发布/订阅频道里发布内容.

“random”: 该命令接收相同的输入可能会有不同的输出

“allow-stale”: 该命令允许在slave上执行,即使slave提供的数据不一致。

“no-monitor”: 不监控该命令。如果命令参数有敏感数据可以使用该设置

“fast”: 该命令的时间复杂度应当低于O(log(N)),其中N是集合的大小等

“getkeys-api”: 该命令实现了返回keys的接口。

“no-cluster”: 该命令不在Redis群集中注册,因为该命令不是为群集设计的。

3.3判断布隆过滤是否存在

bloom 链条结构:

typedefstructSBChain {
   SBLink *filters;  //< 当前过滤器
   size_t size;      //< 所有过滤器中的项目总数
   size_t nfilters;  //< 链节数
   unsigned options; //< 直接传递给bloom_init的选项
} SBChain;

bloom link结构:

typedefstructSBLink {
   structbloominner;//< bloom内部结构
   size_t size;        // < 链接中的项目数
} SBLink;

bloom结构

structbloom {
   uint32_t hashes;   //哈希 hashes = bpe * ln(2)
   uint8_t force64;   //总是强制64位哈希,即使太小
   uint8_t n2;       //取模使用,这个参数是用于64和32的取模
   uint32_t entries; //条目个数:要插入的条目的预期数目。必须至少1000(实际上,可能要大得多)。

   double error;      //错误率:碰撞概率(只要条目不是超出)。
   double bpe;        //字节对编码,用于计算。

   unsignedchar *bf; //过滤器字符串
   size_t bytes;      //字节数
   uint32_t bits;     //位数,最佳位数bits = (entries * ln(error)) / ln(2)^2
};

判断布隆命令是否存在方法:

/**
* Check for the existence of an item
* BF.CHECK
* Returns true or false
*/

staticintBFCheck_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
   RedisModule_AutoMemory(ctx);
   int is_multi = isMulti(argv[0]);

   if ((is_multi == 0 && argc != 3) || (is_multi && argc < 3)) {
       RedisModule_WrongArity(ctx);
       return REDISMODULE_ERR;
   }

   RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ);
   SBChain *sb;
   int status = bfGetChain(key, &sb); //获得key链条

   int is_empty = 0;
   if (status != SB_OK) { .  //判断key是否为空
       is_empty = 1;
   }

   // Check if it exists?
   if (is_multi) {
       RedisModule_ReplyWithArray(ctx, argc - 2);
   }

   for (size_t ii = 2; ii < argc; ++ii) {
       if (is_empty == 1) {   //空情况下直接返回0
           RedisModule_ReplyWithLongLong(ctx, 0);  
       } else {
           size_t n;
           constchar *s = RedisModule_StringPtrLen(argv[ii], &n);
           int exists = SBChain_Check(sb, s, n);   //检测值是否存在
           RedisModule_ReplyWithLongLong(ctx, exists);
       }
   }

   return REDISMODULE_OK;
}

检测链条值是否存在

intSBChain_Check(const SBChain *sb, constvoid *data, size_t len) {
   bloom_hashval hv = SBChain_GetHash(sb, data, len); //计算链条hansh
   for (int ii = sb->nfilters - 1; ii >= 0; --ii) {
       if (bloom_check_h(&sb->filters[ii].inner, hv)) { .  
           return1;
       }
   }
   return0;
}

//通过hash值,计算映射到字符串的哪个字节上
intbloom_check_h(const struct bloom *bloom, bloom_hashval hash) {
   if (bloom->force64 || bloom->n2 > 31) {
       return bloom_check_add64((void *)bloom, hash, MODE_READ);
   } elseif (bloom->n2 > 0) {
       return bloom_check_add32((void *)bloom, hash, MODE_READ);
   } else {
       return bloom_check_add_compat((void *)bloom, hash, MODE_READ);
   }
}

#define CHECK_ADD_FUNC(T, modExp)                                                                  \
   T i;                                                                                           \
   int found_unset = 0;                                                                           \
   const register T mod = modExp;                                                                 \
   for (i = 0; i < bloom->hashes; i++) {                                                          \

       T x = ((hashval.a + i * hashval.b)) % mod;                                                 \
       if (!test_bit_set_bit(bloom->bf, x, mode)) {                                               \
           if (mode == MODE_READ) {                                                               \
               return0;                                                                          \
           }                                                                                      \
           found_unset = 1;                                                                       \
       }                                                                                          \
   }                                                                                              \
   if (mode == MODE_READ) {                                                                       \
       return1;                                                                                  \
   }                                                                                              \
   return found_unset;
//32位检测  
staticintbloom_check_add32(struct bloom *bloom, bloom_hashval hashval, int mode) {
   CHECK_ADD_FUNC(uint32_t, (1 << bloom->n2));
}
//64位检测
staticintbloom_check_add64(struct bloom *bloom, bloom_hashval hashval, int mode) {
   CHECK_ADD_FUNC(uint64_t, (1LLU << bloom->n2));
}

判断bf字符串存放hash位置

inlinestaticinttest_bit_set_bit(unsignedchar *buf, uint64_t x, int mode) {
   uint64_t byte = x >> 3;
   uint8_t mask = 1 << (x % 8);
   uint8_t c = buf[byte]; // 获得对应的字节,高昂的内存访问

   if (c & mask) {
       return1;
   } else {
       if (mode == MODE_WRITE) { //写字节
           buf[byte] = c | mask;
       }
       return0;
   }
}

通过计算出来的hash取模计算bf字符串的值是否设置,这一块很类似我们的bit存储结构。

总结:

1.布隆过滤器是redis的一个插件功能;

2.布隆过滤器有很多使用场景比如说反垃圾邮件,从数十亿的垃圾邮件列表中判断某邮件是否为垃圾。或者说解决缓存击穿问题。或者在一些去重场景中都可以使用到,例如推送新闻不重复。

3.bloomfilter过滤器插件除了布隆过滤器以外,还有布谷鸟过滤器。布谷鸟过滤器会比布隆过滤器多一个DEl功能。而且携带统计功能。

4.RedisModule_OnLoad在每个插件中都会存在、RedisModule_Init用于初始化Module信息、RedisModule_CreateCommand注册的一个命令。

5.数据存储在bloom 中的bf字符串中,是通过hash取模后。存储到bf字符串中。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
1天前
|
缓存 NoSQL Redis
Redis 工具类 与 Redis 布隆过滤器
Redis 工具类 与 Redis 布隆过滤器
10 1
|
1天前
|
存储 NoSQL Redis
Redis源码、面试指南(5)多机数据库、复制、哨兵、集群(下)
Redis源码、面试指南(5)多机数据库、复制、哨兵、集群
18 1
|
1天前
|
监控 NoSQL Redis
Redis源码、面试指南(5)多机数据库、复制、哨兵、集群(上)
Redis源码、面试指南(5)多机数据库、复制、哨兵、集群
32 0
|
1天前
|
存储 NoSQL 调度
Redis源码、面试指南(4)单机数据库、持久化、通知与订阅(下)
Redis源码、面试指南(4)单机数据库、持久化、通知与订阅
8 0
|
1天前
|
NoSQL 安全 Unix
Redis源码、面试指南(4)单机数据库、持久化、通知与订阅(中)
Redis源码、面试指南(4)单机数据库、持久化、通知与订阅
15 0
|
1天前
|
存储 NoSQL API
Redis源码、面试指南(4)单机数据库、持久化、通知与订阅(上)
Redis源码、面试指南(4)单机数据库、持久化、通知与订阅
15 1
|
1天前
|
NoSQL API Redis
Redis源码、面试指南(3)数据对象类型编码(下)
Redis源码、面试指南(3)数据对象类型编码
10 1
|
1天前
|
存储 NoSQL API
Redis源码、面试指南(3)数据对象类型编码(上)
Redis源码、面试指南(3)数据对象类型编码
16 2
|
1天前
|
存储 NoSQL 算法
Redis源码、面试指南(2)内存编码数据结构(下)
Redis源码、面试指南(2)内存编码数据结构
18 4
|
1天前
|
存储 NoSQL API
Redis源码、面试指南(2)内存编码数据结构(上)
Redis源码、面试指南(2)内存编码数据结构
13 0