Redis基于布隆过滤器解决缓存穿透问题
1.布隆过滤器基本介绍
布隆过滤器适用于判断某个数据是否在集合中存在,可能存在一定的误判, Bloom Filter基本实现原理采用位数组与联合函数一起实现;实现的原理采用二进制向量数组和随机映射hash函数。
布隆过滤器为什么会产生冲突 ,会根据key计算hash值,可能与布隆过滤器中存放的元素hash产生冲突都是为1,布隆可能会产生误判可能存在。
如何解决这个问题,可以将二进制数组长度设置比较大,可以减少布隆误判的概率。
【适合的场景】
- 防止缓存穿透直接访问数据库
- 判断用户是否阅读过某一个视频或文章;类似抖音,快手,可能误判,但是不会看到重复的内容
- 做黑名单过滤,针对不同的用户是否存在白名单和黑名单,可能误判,但一定程度可以解决问题等等
2.布隆过滤器的优缺点
优点
-
支持海量数据场景下高效的判断元素是否存在
-
空间占用极小,因为本身不存储数据而是用比特位表示数据是否存在,某种程度有保密的效果。
缺点
-
不存储数据本身,所以只能添加但不可以删除,因为删除会导致误判率增加
-
由于存在hash碰撞,不一定百分百判断准确,存在一定误差
3.布隆过滤器的原理
布隆过滤器的原理是,当一个元素被加入集合时,通过 K 个散列函数将这个元素映射成一个位数组中的 K 个点(offset),把它们置为 1。检索时,我们只要看看这些点是不是都是 1 就(大约)知道集合中有没有它了:如果这些点有任何一个 0,则被检元素一定不在;如果都是 1,则被检元素很可能在。这就是布隆过滤器的基本思想。
简单来说就是准备一个长度为 m 的位数组并初始化所有元素为 0,用 k 个散列函数对元素进行 k 次散列运算跟 len(m)取余得到 k 个位置并将 m 中对应位置设置为 1。
4.缓存穿透问题
缓存穿透是指使用不存在的key进行大量的高并发查询,导致缓存无法命中,每次请求都要都要穿透到后端数据库查询,使得数据库的压力非常大,甚至导致数据库服务卡死;
应对的方案:
- 对我们的服务接口api实现限流、用户授权、黑名单和白名单拦截;
- 从缓存和数据库都查询不到结果的话,将数据库空值结果缓存到Redis中;设置合理过期时间(如:5s)避免使用同一个id对数据库攻击。
如果黑客真的在攻击的情况下,随机成id肯定是不一样的,可采用布隆过滤器。 - 布隆过滤器
xml
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.2-jre</version>
</dependency>
java
public class BlongTest {
/**
* 在布隆中存放100万条数据
*/
private static Integer size = 1000000;
public static void main(String[] args) {
BloomFilter<Integer> integerBloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, 0.01);
for (int i = 0; i < size; i++) {
integerBloomFilter.put(i);
}
// 从布隆中查询数据是否存在
ArrayList<Integer> strings = new ArrayList<>();
for (int j = size; j < size + 10000; j++) {
if (integerBloomFilter.mightContain(j)) {
strings.add(j);
}
}
System.out.println("误判数量:" + strings.size());
}
}
5.解决Redis缓存穿透问题
java
public static BloomFilter<Integer> integerBloomFilter = null;
@RequestMapping("/getOrder")
public OrderEntity getOrder(Integer orderId) {
if (integerBloomFilter != null) {
if (!integerBloomFilter.mightContain(orderId)) {
System.out.println("从布隆过滤器中检测到该key不存在");
return null;
}
}
// 1.先查询Redis中数据是否存在
OrderEntity orderRedisEntity = (OrderEntity) redisTemplateUtils.getObject(orderId + "");
if (orderRedisEntity != null) {
System.out.println("直接从Redis中返回数据");
return orderRedisEntity;
}
// 2. 查询数据库的内容
System.out.println("从DB查询数据");
OrderEntity orderDBEntity = orderMapper.getOrderById(orderId);
if (orderDBEntity != null) {
System.out.println("将Db数据放入到Redis中");
redisTemplateUtils.setObject(orderId + "", orderDBEntity);
}
return orderDBEntity;
}
@RequestMapping("/dbToBulong")
public String dbToBulong() {
List<Integer> orderIds = orderMapper.getOrderIds();
integerBloomFilter = BloomFilter.create(Funnels.integerFunnel(), orderIds.size(), 0.01);
for (int i = 0; i < orderIds.size(); i++) {
integerBloomFilter.put(orderIds.get(i));
}
return "success";
}
计算布隆过滤器在线网址:布隆过滤计算器
Guava 提供的布隆过滤器的实现还是很不错的,但是它有一个重大的缺陷就是只能单机使用,而现在互联网一般都是分布式的场景。为了解决这个问题就需要用到Redis中的布隆过滤器。