1 布隆过滤器
1.1 说明
百度百科里面对布隆过滤器的说明如下:
如果想要判断一个元素是不是在一个集合里,一般想到的是将所有元素保存起来,然后通过比较确定。[链表],树等等数据结构都是这种思路. 但是随着集合中元素的增加,我们需要的存储空间越来越大,检索速度也越来越慢(O(n),O(logn))。不过世界上还有一种叫作散列表(又叫[哈希表],Hash table)的数据结构。它可以通过一个[Hash函数]将一个元素映射成一个位阵列(Bit array)中的一个点。这样一来,我们只要看看这个点是不是1就可以知道集合中有没有它了。
举一个例子,有一个长度为8位的字节数组
0|0|0|0|0|0|0|0
字符串tom通过hash计算得到3,5,那么修改位置3和位置5
0|0|0|1|0|1|0|0
字符串jack通过hash计算得到0,1,3
1|1|0|1|0|1|0|0
字符串kk通过hash计算得到1,3,5 但是1,3,5的位置已经被别人填充过了,那么如果判断1,3,5的位置是否都是1,标识存在kk,就会出错,实际上kk没有存进去,存进去的是tom和jack,就会出现一定的误差.
如果现在对字符串mm进行hash,得到6,7,现在6,7的位置都是0,那么mm肯定不存在改字节数组中.
1.2 结论
所以得到布隆过滤器的两个特性
1 如果判断不存在布隆过滤器中,那么就一定不存在 .例如mm
2 如果存在布隆过滤器中,那么可能不存在 .例如kk
1.3 应用
那么布隆过滤器该如何应用呢
1 爬虫的url去重: 一般我们爬虫通过递归得到的url很容易重复,比如首页可能有一个个人中心的超链接,个人中心的页面有一个首页的超链接,但是我们并不想重复爬取已经爬过的界面,如果用键值对的方式对于大网站很多url就会容易内存泄漏,使用布隆过滤器的方式虽然有一定的误判率,但是基本满足需求
2 缓存击穿: 面试经常提到的redis缓存击穿,如果查询一个数据库不存在的key,因为数据库也没有,redis也没有缓存上,那么请求很多就会直接怼到数据库上,造成缓存击穿.如果使用布隆过滤器先把所有的key放进去,如果不存在布隆过滤器中,那么数据库中肯定也不存在,可以直接返回给用户,不用数据库再查一遍了
2 redis安装布隆过滤器模块
2.1 说明
为什么redis适合用来做布隆过滤器呢,主要是因为redis有一个Bitmaps的结构,可以理解为以位为单位的数组,每个位只能存储0和1,例如
0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0
redis最大能够存储512M的Bitmaps
1M = 1024k
1k = 1024字节
1字节 = 8位
所以 512M = 512x1024x1024x8 = 4294967296,可以存储40多亿的位数
2.2 安装
# 默认redis已经安装好了 文章redis是安装在/usr/local/redis下的
# 进入到redis目录
cd /usr/local/redis
# 创建module文件夹
mkdir module && cd module
# 如果没有安装git 可以安装一下git
yum -y install git
# 克隆redisbloom源文件
git clone https://github.com/RedisLabsModules/redisbloom.git
# 进入redisbloom文件夹
cd redisbloom/
# 编译
make
# 编译后会得到一个redisbloom.so的文件 然后在redis.conf增加如下配置
loadmodule ../module/redisbloom/redisbloom.so
# 重启redis
pkill redis
/usr/local/redis/bin/redis-server /usr/local/redis/etc/redis.conf
2.3 布隆过滤器的基本命令
bf.reserve(key, error_rate, capacity)
● key 键
● error_rate 错误比例,该比例越小,则需要的空间越大
● capacity 容量,当实际容量超过这个数的时候,误判率会增加
bf.add(key, value)
● key 键
● value 值
bf.exists(key, value)
● key 键
● value 值
3 java实现基于redis的布隆过滤器
3.1 初始化布隆过滤器
public boolean initBloomFilter(String key,Long errorRate,Integer count){
String script = "redis.call('bf.reserve', KEYS[1], ARGV[1], ARGV[2])";
RedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class);
Object result = redisTemplate.execute(redisScript,genericToStringSerializer,genericToStringSerializer, Collections.singletonList(key),errorRate,count);
if(result!=null && OK.equals(result.toString())){
return true;
}
return false;
}
3.2 向布隆过滤器添加数据
public boolean addBloomFilter(String key,String value){
String script = "redis.call('bf.add', KEYS[1], ARGV[1])";
RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
Object result = redisTemplate.execute(redisScript,genericToStringSerializer,genericToStringSerializer, Collections.singletonList(key),value);
if(SUCCESS.equals(result)){
return true;
}
return false;
}
3.3 判断布隆过滤器是否存在数据
public boolean existBloomFilter(String key,String value){
String script = "redis.call('bf.exists', KEYS[1], ARGV[1])";
RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
Object result = redisTemplate.execute(redisScript,genericToStringSerializer,genericToStringSerializer, Collections.singletonList(key),value);
if(SUCCESS.equals(result)){
return true;
}
return false;
}