第九节:Redis的Bloom Filter原理、实操、以及应用场景(缓存穿透、黑名单校验等)详解
一. Bloom Filte介绍
1. 含义
(1). 布隆过滤器(Bloom Filter)是由Howard Bloom在1970年提出的一种比较巧妙的概率型数据结构,它实际上是由一个很长的二进制(0或1)向量和一系列随机映射函数组成。
(2). 布隆过滤器可以用于检索一个元素是否在一个集合中。它可以告诉你某种东西一定不存在或者可能存在。当布隆过滤器说,某种东西存在时,这种东西可能不存在;当布隆过滤器说,某种东西不存在时,那么这种东西一定不存在。
(3). 布隆过滤器 优点:A. 空间效率高,占用空间少 B. 查询时间短
缺点:A. 有一定的误判率 B. 元素不能删除
2. 原理
当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点(使用多个哈希函数对元素key (bloom中不存value) 进行哈希,算出一个整数索引值,然后对位数组长度进行取模运算得到一个位置,每个无偏哈希函数都会得到一个不同的位置),把它们置为1。
检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:① 如果这些点有任何一个为0(如下图的e),则被检元素一定不在;如果都是1(如下图的d),并不能完全说明这个元素就一定存在其中,有可能这些位置为1是因为其他元素的存在,这就是布隆过滤器会出现误判的原因。
如下图:
补充:
Bloom Filter跟 ‘’单哈希函数BitMap‘’ 不同之处在于:Bloom Filter使用了k个哈希函数,每个字符串跟k个bit对应,从而降低了冲突的概率。
3. 实现
(1). Redis的bitmap
基于redis的 bitmap数据结构 的相关指令来执行。
(2). RedisBloom (推荐)
布隆过滤器可以使用Redis中的位图(bitmap)操作实现,直到Redis4.0版本提供了插件功能,Redis官方提供的布隆过滤器才正式登场,布隆过滤器作为一个插件加载到Redis Server中,官网推荐了一个 RedisBloom 作为 Redis 布隆过滤器的 Module。
详细安装、指令操作参考:https://github.com/RedisBloom/RedisBloom
文档地址:https://oss.redislabs.com/redisbloom/
(3). PyreBloom
(4). Lua脚本实现
详见:https://github.com/erikdubbelboer/redis-lua-scaling-bloom-filter
(5). guvua包自带的布隆过滤器
防止缓存穿透的业务伪代码分享:
import com.google.common.hash.BloomFilter; //初始化布隆过滤器 //1000:期望存入的数据个数,0.001:期望的误差率 BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.forName("utf‐8")), 1000, 0.001); //把所有数据存入布隆过滤器 void init(){ for (String key: keys) { bloomFilter.put(key); } } String get(String key) { // 从布隆过滤器这一级缓存判断下key是否存在 Boolean exist = bloomFilter.mightContain(key); if(!exist){ return ""; } // 从缓存中获取数据 String cacheValue = cache.get(key); // 缓存为空 if (StringUtils.isBlank(cacheValue)) { // 从存储中获取 String storageValue = storage.get(key); cache.set(key, storageValue); // 如果存储数据为空, 需要设置一个过期时间(300秒) if (storageValue == null) { cache.expire(key, 60 * 5); } return storageValue; } else { // 缓存非空 return cacheValue; } }
二. RedisBloom实操
1. 简介
RedisBloom模块提供了四种数据类型:Bloom Filter (布隆过滤器)、Cuckoo Filter(布谷鸟过滤器)、 Count-Mins-Sketch、 Top-K 。 Bloom Filter和 Cuckoo 用于确定(以给定的确定性)集合中是否存在某项。使用 Count-Min Sketch 来估算子线性空间中的项目数,使用Top-K 维护K个最频繁项目的列表,本节重点介绍 Bloom Filter的使用。
参考官网:https://github.com/RedisBloom/RedisBloom
https://oss.redislabs.com/redisbloom/
连接RedisBloom的客户端如下,其中.Net客户端是 StackExchange.Redis的一个扩展类(https://gist.github.com/naile/96de4e9548c7b5fd6c0614009ffec755),源码的本质就是 Excute 执行RedisBloom指令 的封装了一下而已。
2. 安装
(1). 基于docker安装
#1. 下载最新版本的镜像
docker pull redislabs/rebloom:latest
#2. 发布成容器,容器名为 redis-redisbloom docker run -p 6379:6379 --name redis-redisbloom redislabs/rebloom:latest
#3. 进入容器内部 docker exec -it redis-redisbloom bash # redis-cli # 127.0.0.1:6379> bf.add tiancheng hello
(2). 直接编译安装
详见: https://www.cnblogs.com/yaopengfei/p/17587045.html
3. 指令介绍
(1). 指令
#1.添加单个元素到布隆过滤器
bf.add filterName key
#2. 判断单个元素是否在布隆过滤器汇总 bf.exists filterName key
#3. 添加多个元素到布隆过滤器 bf.madd filterName key1 key2 key3
#4. 判断多个元素是否在布隆过滤器 bf.mexists filterName key1 key2 key3
#高级用法(配置期望错误率 和初始容量)
#上述bf.add命令如果过滤器没有创建的时候,会自动创建,并且使用的默认参数
# bf.reserve指令,创建一个自定义过滤器,有三个参数, 创建完成后,再执行bf.add指令
# filterName: 过滤器名称
# error_rate: 期望错误率,期望错误率越低,需要的空间就越大
# capacity:初始容量,当实际元素的数量超过这个初始容量的时候,误判率会上升。
eg:
bf.reserve ypfFilter1 0.0001 1000000
PS:如果对应的key已经存在时,在执行bf.reserve
命令就会报错。如果不使用bf.reserve
命令创建,而是使用Redis自动创建的布隆过滤器,默认的error_rate
是 0.01,capacity
是 100。
布隆过滤器的error_rate
越小,需要的存储空间就越大,对于不需要过于精确的场景,error_rate
设置稍大一点也可以。布隆过滤器的capacity
设置的过大,会浪费存储空间,设置的过小,就会影响准确率,所以在使用之前一定要尽可能地精确估计好元素数量,还需要加上一定的冗余空间以避免实际元素可能会意外高出设置值很多。总之,error_rate
和 capacity
都需要设置一个合适的数值。
三. 应用场景
1. 解决缓存穿透
(1). 含义
业务请求中数据缓存中没有,DB中也没有,导致类似请求直接跨过缓存,反复在DB中查询,与此同时缓存也不会得到更新。(详见:https://www.cnblogs.com/yaopengfei/p/13878124.html)
(2). 解决思路
事先把存在的key都放到redis的Bloom Filter 中,他的用途就是存在性检测,如果 BloomFilter 中不存在,那么数据一定不存在;如果 BloomFilter 中存在,实际数据也有可能会不存在。
剖析:布隆过滤器可能会误判,放过部分请求,当不影响整体,所以目前该方案是处理此类问题最佳方案。
2. 黑名单校验
识别垃圾邮件,只要发送者在黑名单中的,就识别为垃圾邮件。假设黑名单的数量是数以亿计的,存放起来就是非常耗费存储空间的,布隆过滤器则是一个较好的解决方案。把所有黑名单都放在布隆过滤器中,再收到邮件时,判断邮件地址是否在布隆过滤器中即可。
ps:
如果用哈希表,每存储一亿个 email地址,就需要 1.6GB的内存(用哈希表实现的具体办法是将每一个 email地址对应成一个八字节的信息指纹,然后将这些信息指纹存入哈希表,由于哈希表的存储效率一般只有 50%,因此一个 email地址需要占用十六个字节。一亿个地址大约要 1.6GB,即十六亿字节的内存)。因此存贮几十亿个邮件地址可能需要上百 GB的内存。而Bloom Filter只需要哈希表 1/8到 1/4 的大小就能解决同样的问题。
3. Web拦截器
(1). 含义
如果相同请求则拦截,防止重复被攻击。
(2). 解决思路
用户第一次请求,将请求参数放入布隆过滤器中,当第二次请求时,先判断请求参数是否被布隆过滤器命中,从而提高缓存命中率。
参考文章:https://github.com/luw2007/bloomfilter/blob/master/doc/bloomfilter_in_action.md
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。