前言
我们前两天讲了布隆过滤器的底层原理和用途,有小伙伴表示没听爽。那老哥只能再来一次了,谁让我是你们的老哥呢。(默默感动就好)
这篇文章我们主要讲布隆过滤器的使用,比如缓存雪崩、视频推送等案例来讲解。如果对布隆过滤器原理不熟悉的小伙伴,可以去历史文章里读一下之前的文章。
Redis缓存穿透解决案例
其实布隆过滤器本质来讲,就是起到一个黑名单或者白名单的作用。我们从这两个角度去分析缓存穿透问题。
白名单解决缓存穿透
注意问题
- 如果没在白名单里的数据被误判存在于过滤器里的话,会穿透到数据库,不过误判的几率本来就很小,所以穿透问题不大。
- 必须将所有的查询key都放到布隆过滤器和Redis里,否则请求会被直接返回空数据。
代码实现
老哥按照这个流程图给大家一份代码,其他几种情况,大家思考怎么去实现。
pom
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.13.4</version>
</dependency>
Java代码
import com.alibaba.fastjson.JSON;
import com.bilibili.itlaoge.model.User;
import org.redisson.Redisson;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
/**
* 解决缓存穿透—白名单
*/
public class RedissonBloomFilter {
/**
* 构造Redisson
*/
static RedissonClient redisson = null;
static RBloomFilter<String> bloomFilter = null;
static {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
//构造Redisson
redisson = Redisson.create(config);
//构造布隆过滤器
bloomFilter = redisson.getBloomFilter("userIdFilter");
// 将查询数据放入Redis缓存和布隆过滤器里
initData(redisson, bloomFilter);
}
private static void initData(RedissonClient redisson, RBloomFilter<String> bloomFilter) {
//初始化布隆过滤器:预计元素为100000000L,误差率为3%
bloomFilter.tryInit(100000000L,0.01);
//将id为1的数据,插入到布隆过滤器中
bloomFilter.add("1");
bloomFilter.add("2");
// 将id为1对应的user数据,插入到Redis缓存中
redisson.getBucket("1").set("{id:1, userName:'张三', age:18}");
}
public static void main(String[] args) {
User user = getUserById(2L);
System.out.println("user对象为:" + JSON.toJSONString(user));
}
public static User getUserById(Long id) {
if (null == id) {
return null;
}
String idKey = id.toString();
// 开始模拟缓存穿透
// 前端查询请求key
if (bloomFilter.contains(idKey)) {
// 通过了过滤器白名单校验,去Redis里查询真正的数据
RBucket<Object> bucket = redisson.getBucket(idKey);
Object object = bucket.get();
// 如果Redis有数据,直接返回该数据
if (null != object) {
System.out.println("从Redis里面查询出来的");
String userStr = object.toString();
return JSON.parseObject(userStr, User.class);
}
// 如果Redis为空,去查询数据库
User user = selectByDb(idKey);
if (null == user) {
return null;
} else {
// 将数据重新刷进缓存
redisson.getBucket(id.toString()).set(JSON.toJSONString(user));
}
return user;
}
return null;
}
private static User selectByDb(String id) {
System.out.println("从MySQL里面查询出来的");
User user = new User();
user.setId(1L);
user.setUserName("张三");
user.setAge(18);
return user;
}
}
user对象
/**
* 用户实体类
* @author hp
*/
public class User implements Serializable {
public static String maYunPhone = "18890019390";
private Long id;
/**
* 用户名
*/
private String userName;
/**
* 年龄
*/
private Integer age;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
黑名单解决缓存穿透
注意问题
- 布隆过滤器里的数据,存在误判,如果正常数据被误判存在黑名单里的话,会直接返回空数据。
- 黑名单里的数据要很全面才行,否则会有比较严重的穿透问题。
- 本来是在黑名单里的非法数据,之后有可能是正常数据。如:用id大于100万的数来请求,我们数据库里只有10万数据,这时候如果把id放进黑名单里。等数据达到100万的时候,就会出现问题。
思考题
如果黑名单的方式和白名单的方式结合起来,大家知道怎么去使用吗?希望大家能多去思考一下。
应用场景再举例
以下场景仅是举例,不涉及具体业务实现。目的是让大家更好的明白布隆过滤器,作为黑名单和白名单的使用。
视频推送场景(黑名单)
背景:某视频网站给用户推送视频
布隆过滤器作用:当黑名单使用。
要求:对于某用户,已经推送过的视频,不在进行推送。
流程:当推送给用户一批视频时,先判断这些视频是否存在过滤器里;如果存在就不推送给用户,不存在就推送给用户;同时将推送过的视频存入过滤器黑名单里,防止下次重复推送。
转载视频/文章案例(白名单)
背景:某用户想转载老哥的文章。
布隆过滤器作用:当白名单使用。
要求:在老哥转发白名单里的,有转发文章的权限。
流程:某用户想转发老哥的文章,由于没在白名单里,转发失败。于是找到老哥开白名单,老哥把他加入了白名单里后,允许转发了。
结语
没有完美的技术,每个技术都有优缺点。一项技术不可能适用于所有业务,我们要做的是扬长避短,利用技术的优点去实现我们的业务需求。
就像我们每个人都有优点和缺点,我们要挖掘我们的长处,去做适合我们的事情。
就好比让我去当CEO,CEO来给我写代码,虽然心里很爽啊,但是估计过不了多久,公司就黄了。