前言
在上文中详细提到了在Linux中使用Docker安装Redis容器以及手动安装布隆过滤器插件
文章地址:
本文将整合项目来介绍如何使用布隆过滤器插件。
概念
在这里先简单介绍一下缓存穿透的概念:key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据源,从而可能压垮数据源(DB服务器)。
即查询一个不存在的数据,数据库mysql中查询不到也不会写入缓存,就导致每次请求都查到数据库。 比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,此漏洞可能压垮数据库。
至于布隆过滤器的原理,大家可以去搜索一下,有许多写的很好的文章,此处就不多做赘述。
解决方式:
方式一:
缓存空数据,当查询数据返回为空时,任然给这个空结果缓存,即赋null值。并设置一个比较短的过期时间。(不超过5分钟)。
优点:简单。
缺点:消耗内存,可能会发生不一致。
方案二:
使用布隆过滤器。
其工作方式就是将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
JAVA项目整合
官方文档地址:GitHub - RedisBloom/JRedisBloom: Java Client for RedisBloom probabilistic module
导入依赖:
<dependency>
<groupId>com.redislabs</groupId>
<artifactId>jrebloom</artifactId>
<version>2.2.2</version>
</dependency>
基本api使用demo:
/**JreBloom提供的jedis可以操控redis中的布隆过滤器********************************/
@Test
public void testJedis(){
//经过测试此是操作redis中的布隆过滤器
//改为jedisPool连接池
//Client支持各种方式
// Jedis jedis= JedisUtil.getJedis();
// Client client=new Client(jedis);
Client client=new Client("127.0.0.1",6379);
//测试是否和redis容器同步
System.out.println(client.exists("phoneid", "9999"));
System.out.println(client.exists("phoneid", "6666"));
//单个添加操作
client.add("phoneid","0716");
client.exists("phoneid","0716");
//批量添加
client.addMulti("phoneid","name1","name2","name3");
boolean[] booleans = client.existsMulti("phoneid", "name1", "name2", "name3", "name4");
for (int i = 0; i < booleans.length; i++) {
System.out.println("第"+i+booleans[i]+"个");
}
//创建一个过滤器 key 容量 容错率
client.createFilter("userid",100000,0.01); //如若重复创建会报错
//需要进行异常处理
client.bfInsert("userid","999");
//两者都是往布隆过滤器中插值
System.out.println(client.add("userid", "999"));//add方法如果元素存在则返回false 应该返回false
boolean[] userids = client.bfInsert("userid", "999");//如果元素存在则覆盖值
for (boolean userid : userids) {
System.out.println(userid);
}
// client.delete(key) 删除过滤器
}
小插曲:
起初以为其提供的Client中使用的Jedis没有使用JedisPool连接池,后经过自己配置了一个Jedis连接池后,测试单例模式时发现其Client对象中有一个JedisPool。
后经过源码查询发现,Client支持的几种方式。其有自带的JedisPool。
官方文档演示默认演示是这种方式。
整合业务
示例图:
本项目以电商系统为例,业务实现案例:
//详情业务 加入Redis TODO 加入布隆过滤器 防止缓存穿透
@Override
public List<Phone> findPhoneByPid(Integer pid) {
List<Phone> list=null;
Client client=JedisClientSingleton.getInstance(); //利用双重检查所确保client对象单例
//防止缓存穿透 先从 布隆过滤器中查询--->redis---->DB 未查询到则直接返回
if(client.exists("phoneid", String.valueOf(pid))){ //布隆过滤器判断 如果不存在 则一定不存在于缓存和DB中 直接返回
if (this.cacheService.hasKey(String.valueOf(pid))) { //hasKey(key) 判断是否存在该key
list=this.cacheService.getList(pid,Phone.class); //如果存在该Key 则从Redis中拿取
}else {
list=comDao.selectPhoneByPid(pid); //否则走DB 从数据库中拿取
this.cacheService.add(pid,list,EXPIRE_TIME, EXPIRE_TIME_TYPE); // 然后将该List数据存入Redis 并设置过期时间 TODO 过期时间应该随机设置--防止缓存雪崩
//那么下次则会走Redis 而不会走DB
}
return list;
}
return null; //不存在直接返回空
}
BUG记录:
在创建好一个布隆过滤器后,将虚拟机重启一次后发现布隆过滤器不存在,后通过命令查看redis数据持久化是否开启CONFIG GET appendonly结果返回no,但配置文件中已经修改为yes,后检查,发现是当时docker命令启动redis的命令有问题,进入容器中检查配置文件发现目录中没有挂载的配置文件,后发现启动命令目录弄错了,于是修改为正确的目录和文件。后解决此问题