是什么
由一个初值都为零的bit
数组和多个哈希函数构成,来判断某个数据是否存在。
特点:
- 占用空间少,高效插入和查询
- 存在误判的情况,因为不同的Key可能计算出相同的hash值
- 一个元素如果判断结果为不存在则一定不存在!
原理
- 初始化
- 添加:当我们向布隆过滤器中添加数据时,为了尽量地址不冲突,会使用多个 hash 函数对 key 进行运算,算得一个下标索引值,然后对位数组长度进行取模运算得到一个位置,每个 hash 函数都会算得一个不同的位置。再把位数组的这几个位置都置为 1 就完成了 add 操作。
- 判断是否存在:向布隆过滤器查询某个key是否存在时,先把这个 key 通过相同的多个 hash 函数进行运算,查看对应的位置是否都为 1,
只要有一个位为 0,那么说明布隆过滤器中这个 key 不存在;
如果这几个位置全都是 1,那么说明极有可能存在;
使用场景
- 解决
redis
缓存穿透问题
使用Docker安装 带有RedisBloom
的镜像文件
public class RedissonBloomFilterDemo
{
public static final int _1W = 10000;
//布隆过滤器里预计要插入多少数据
public static int size = 100 * _1W;
//误判率,它越小误判的个数也就越少
public static double fpp = 0.03;
static RedissonClient redissonClient = null;
static RBloomFilter rBloomFilter = null;
static
{
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.111.147:6379").setDatabase(0);
//构造redisson
redissonClient = Redisson.create(config);
//通过redisson构造rBloomFilter
rBloomFilter = redissonClient.getBloomFilter("phoneListBloomFilter",new StringCodec());
rBloomFilter.tryInit(size,fpp);
// 1测试 布隆过滤器有+redis有
rBloomFilter.add("10086");
redissonClient.getBucket("10086",new StringCodec()).set("chinamobile10086");
// 2测试 布隆过滤器有+redis无
//rBloomFilter.add("10087");
//3 测试 ,都没有
}
public static void main(String[] args)
{
String phoneListById = getPhoneListById("10087");
System.out.println("------查询出来的结果: "+phoneListById);
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
redissonClient.shutdown();
}
private static String getPhoneListById(String IDNumber)
{
String result = null;
if (IDNumber == null) {
return null;
}
//1 先去布隆过滤器里面查询
if (rBloomFilter.contains(IDNumber)) {
//2 布隆过滤器里有,再去redis里面查询
// getBucket获取可以操作redis字符串的对象
RBucket<String> rBucket = redissonClient.getBucket(IDNumber, new StringCodec());
result = rBucket.get();
if(result != null)
{
return "i come from redis: "+result;
}else{
result = getPhoneListByMySQL(IDNumber);
if (result == null) {
return null;
}
// 重新将数据更新回redis
redissonClient.getBucket(IDNumber,new StringCodec()).set(result);
}
return "i come from mysql: "+result;
}
return result;
}
private static String getPhoneListByMySQL(String IDNumber)
{
return "chinamobile"+IDNumber;
}
}
补充
- 缓存击穿:热点Key过期
- 热点Key不设置过期时间
- 互斥锁防止缓存击穿
- 随即退避:让线程随机短暂等待,再去检查缓存中是否有数据,没有的话再去数据库查
- 差异失效时间:设置两个缓存a、b,设置不同的过期时间。查询先a后b,更新先b后a,此顺序可以保证即使a缓存没有数据也能从b缓存中获取数据
- 缓存雪崩:大量的Key过期、主机挂掉了
- 搭建Redis集群
- 使用本地缓存(Caffine)+ 限流框架sentinel等
- 开启Redis持久化机制 (AOF + RDB)