布隆过滤器原理
布隆过滤器
布隆过滤器拥有极高的性能,无论是写入操作还是读取操作,时间复杂度是O(1)。
在空间上相对于其他数据结构,有很大优势, 20亿的数据需要 2000000000bit/8/1024/1024 = 238 M ,如果使用数组来存储,假设每个用户 ID 占用 4个字节的空间,存储20亿用户需要 2000000000byte/4/8/1024/1024 = 7600M 的空间,是布隆过滤器的32倍。
布隆过滤器(Bloom Filter)
Bloom
hash 函数
hash
布隆过滤器的缺点
- 在判断元素是否在集合中有一定的错误几率,会误判,把不是在集合中的元素判断为处在集合中。
- 不支持删除元素
布隆过滤器误判是怎么回事?
hash 算法也叫做 摘要算法,作用是对任意一组数据输入,得到一个固定长度的输出摘要。
误判的原因,主要是Hash算法的问题。布隆过滤器是由于一个二进制和一个 Hash 算法组成的,Hash 算法存在着一定的碰撞几率。Hash 碰撞的含义是不同的输入值经过 hash 得到相同的 hash 结果。
hash 算法的输入值是无限的,输出值空间是固定的,比如 16位 hash 值的之空间是 65535 这样碰撞几率就是 1/65535 ,即输入值的个数超过 65535 就一定会发生碰撞。
但是,如果使用更长的 hash 值会带来更高的存储成本和计算成本,32 位的 hash 算法,值空间长度是 2^32-1 大概 42亿,如果有 20亿用户数据,碰撞的概率高达 50%。hash 碰撞造成两个用户,A 和 B 会计算出相同的两个 hash 值,如果A 是注册用户,B不是注册用户, 但是 A 和 B 在的的数组中是相同的,然后产生误判。
布隆过滤器的误判有一个特点,只会出现 false positive 的情况,当布隆过滤器判断元素在集合中,这个元素可能不在集合中,但是布隆过滤器判断这个元素不在集合时,它一定不在集合中,这一点非常适合解决缓存穿透的问题。
布隆过滤器有一个可预测的误判率(FPP):
image
- n 是已经添加元素的数量;
- k 哈希的次数;
- m 布隆过滤器的长度(如比特数组的大小);
怎么减少这个误判几率
布隆过滤器存在误判,但是依然可以减少缓存穿透的发,但是为了尽量减少误判,可以使用如下解决方案:
使用多个 hash 算法为元素计算出多个 Hash 值,只有所有的hash 值对应的数组都为1个小时,才会认定这个元素在集合中。
布隆过滤器不支持删除元素的缺点也合 Hash 碰撞有关
有这样的场景,A 和 B 都是集合中的元素,他没有相同的 hash 值,会映射到数组同一个位置,但是如果此时上次了A,数组中对应位置从1 编程0 ,但是判断 B 时,会发现值是0 ,认为 B 不存在集合中。这样产生误判。
解决这个问题,一般有这样的解决方案:
数组上不再是1 和 0 两个值,而是存储一个计数,如果 A 和 B 命中同一个位置 值就是 2 ,如果 A 删除就把这个值改成1,但是这样数组就不存在 1bit ,而是存储数值,会增加空间的消耗。
布隆过滤器使用
- 选择多个 Hash 函数计算多个 hash 值,可以减少误判
- 布隆过滤器会消耗一定的内存空间,所以在使用时需要评估业务场景需要多大的内存消耗。
如果出现了一个极热数据,一旦失效,会有大量数据穿透到数据库。给数据库造成极大压力。
如何解决热点数据失效问题
极热数据缓存失效,也叫"狗壮效应"
- 热点数据失效后启动一个线程,将数据加载到缓存中,在缓存数据加载前,所有访问的请求都不穿透数据库直接返回。
- 通过 Redis 设置分布式锁,只有获得锁,才能够穿透到数据库。
布隆过滤器使用
pom.xml 依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.0-jre</version>
</dependency>
Java 代码
import com.google.common.base.Charsets;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
public class BloomFilterDemo {
public static void main(String[] args) {
int total = 1000000; // 总数量
BloomFilter<CharSequence> bf =
BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), total);
// 初始化 1000000 条数据到过滤器中
for (int i = 0; i < total; i++) {
bf.put("" + i);
}
// 判断值是否存在过滤器中
int count = 0;
for (int i = 0; i < total + 10000; i++) {
if (bf.mightContain("" + i)) {
count++;
}
}
System.out.println("已匹配数量 " + count);
}
}
总结
布隆过滤器可以一定程度上解决缓存穿透的问题,解决缓存穿透的问题核心是减少数据库的并发访问。由于 hash 碰撞的原因,布隆过滤器存在一定的误判几率,也存在不支持删除元素的问题。
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 深入了解Python 变量作用域
- Laravel5.5 数据库迁移:创建表与修改表示例
- python代码能做成软件吗
- php适配器模式简单应用示例
- Python 解析简单的XML数据
- Laravel 实现Eloquent模型分组查询并返回每个分组的数量 groupBy()
- Python分类测试代码实例汇总
- Swoole 5将移除自动添加Event::wait()特性详解
- laravel 数据验证规则详解
- JS操作XML中DTD介绍及使用方法分析
- PHP设计模式之迭代器(Iterator)模式入门与应用详解
- PHP FileSystem 文件系统常用api整理总结
- laravel框架之数据库查出来的对象实现转化为数组
- php apache开启跨域模式过程详解
- laravel5.6实现数值转换