概述
(1)缓存穿透(Cache Penetration):指查询一个不存在的数据,由于缓存中没有数据,
所以这个查询请求会直接穿过缓存层,到达数据库层,造成了数据库的压力。
攻击者可以通过构造恶意请求,使得缓存层无法命中任何数据,
从而导致请求直接访问数据库,从而引起数据库压力过大。
(2)缓存击穿(Cache Breakdown):指缓存中某个热点数据失效,此时有大量并发请求同时访问
这个失效的数据,导致这些请求直接访问数据库,造成数据库压力过大,
甚至导致数据库崩溃。通常是由于缓存中某个热点数据过期失效,
同时有大量并发请求访问该数据。
(3)缓存雪崩(Cache Avalanche):指缓存中大量的数据失效,导致大量请求直接访问数据库,
造成数据库压力过大。通常是由于缓存中大量的数据在同一时间失效,
导致大量请求直接访问数据库。
(1)缓存穿透:可以在查询缓存之前,先对请求的参数进行合法性检查,如过滤非法字符、
判断参数范围等;或者使用BloomFilter等数据结构,对查询参数进行过滤,
只有在BloomFilter中判断有可能存在的情况下才会去查询数据库。
(2)缓存击穿:可以使用锁机制或者分布式锁机制,避免大量并发请求同时访问失效的热点数据。
或者不设置TTL,设置逻辑上过期标识,需要过期的时候直接删除标识
(3)缓存雪崩:可以采用多级缓存架构,减少缓存层的压力;
或者设置热点数据的过期时间为随机时间,避免在同一时间大量数据同时失效。
另外可以在缓存层和数据库层之间添加限流、熔断等措施,
避免因突发流量导致系统崩溃。
缓存穿透
缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库。
解决方案
1.缓存空对象
简单的来说,就是请求之后,发现数据不存在,就将null值打入Redis中。
优点:实现简单,维护方便
缺点:额外的内存消耗
可能造成短期的不一致
思路分析:
当我们客户端访问不存在的数据时,先请求 redis,但是此时 redis 中没有数据,
此时会访问到数据库,但是数据库中也没有数据,这个数据穿透了缓存,直击数据库,
我们都知道数据库能够承载的并发不如 redis 这么高,如果大量的请求同时过来访问这种不存在的数据,
这些请求就都会访问到数据库,简单的解决方案就是哪怕这个数据在数据库中也不存在,
我们也把这个数据存入到 redis 中去,这样,下次用户过来访问这个不存在的数据,
那么在 redis 中也能找到这个数据就不会进入到数据库了。

2.使用布隆过滤器
在客户端与Redis之间加了一个布隆过滤器,对请求进行过滤。
布隆过滤器的大致原理:布隆过滤器中存放二进制位。
数据库的数据通过hash算法计算其hash值并存放到布隆过滤器中,
之后判断数据是否存在的时候,就是判断该hash值是0还是1。
但是这是一种概率上的统计,当其判断不存在的时候就一定是不存在;
当其判断存在的时候就不一定存在。所以有一定的穿透风险
优点:内存占用较少,没有多余 key
缺点:实现复杂 存在误判可能

3.结合上述两个方案,编码解决
当我们发现,缓存中没有这条记录时,添加这条记录,将值置为null
判断缓存是否命中后在,再判断一下缓存是否为空值

代码实现
@Override
public Result queryById(Long id) {
// 从redis查询商铺缓存
String key = CACHE_SHOP_KEY + id;
String shopJson = stringRedisTemplate.opsForValue().get(key);
// 判断是否存在
if (StrUtil.isNotBlank(shopJson)) {
// 存在,直接返回
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
return Result.ok(shop);
}
// 1.检查缓存中是否有空值
if (shopJson == null) {
// 返回一个错误信息
return Result.fail("店铺不存在!");
}
// 不存在,根据id查询数据库
Shop shop = getById(id);
// 不存在,返回错误
if (shop == null) {
// 2.防止穿透问题,将空值写入redis
stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
return Result.fail("店铺不存在!");
}
// 存在,写入Redis
// 把shop转换成为JSON形式写入Redis
// 同时添加超时时间
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
return Result.ok(shop);
}
总结缓存穿透的解决方案
(1)缓存 null 值
(2)布隆过滤
(3)增强 id 的复杂度,避免被猜测 id 规律
(4)做好数据的基础格式校验
(5)加强用户权限校验
(6)做好热点参数的限流
缓存击穿
缓存击穿是热点key过期,导致大量请求直接打到数据库,造成数据库压力过大

解决方案
互斥锁

逻辑过期
不设置实际的过期时间,只是名义上定义一个过期时间

互斥锁和逻辑过期分析对比

缓存雪崩
缓存雪崩指的是同一时间大量key同时过期或者Redis服务宕机,导致大量请求直接打到数据库,造成数据库崩溃

解决方案
(1)给不同的Key的TTL添加随机值(推荐)
操作简单,当我们在做缓存预热的时候,就有可能在同一时间批量插入大量的数据,
那么如果它们的TTL都一样的话就可能出现大量key同时过期的情况!!!
所以我们需要在设置过期时间TTL的时候,定义一个范围,追加该范围内的一个随机数。
(2)利用Redis集群提高服务的可用性
使用集群提高可靠性
(3)给缓存业务添加降级限流策略
微服务的知识
(4)给业务添加多级缓存
请求到达浏览器,nginx可以做缓存,未命中找Redis,再未命中找JVM,最后到数据库……
布隆过滤器
概述
什么是布隆过滤器
布隆过滤器(Bloom Filter)是 1970 年由布隆提出的,是一种非常节省空间的概率数据结构,运行速度快,占用内存小,但是有一定的误判率且无法删除元素。它实际上是一个很长的二进制向量和一系列随机映射函数组成,主要用于判断一个元素是否在一个集合中。
通常我们都会遇到判断一个元素是否在某个集合中的业务场景,这个时候我们可能都是采用 HashMap的Put方法或者其他集合将数据保存起来,然后进行比较确定,但是如果元素很多的情况下,采用这种方式就会非常浪费空间,最终达到瓶颈,检索速度也会越来越慢,这时布隆过滤器(Bloom Filter)就应运而生了。
布隆过滤器的优点:
支持海量数据场景下高效判断元素是否存在
布隆过滤器存储空间小,并且节省空间,不存储数据本身,仅存储hash结果取模运算后的位标记
不存储数据本身,比较适合某些保密场景
布隆过滤器的缺点:
不存储数据本身,所以只能添加但不可删除,因为删掉元素会导致误判率增加
由于存在hash碰撞,匹配结果如果是“存在于过滤器中”,实际不一定存在
当容量快满时,hash碰撞的概率变大,插入、查询的错误率也就随之增加了
布隆过滤器中一个元素如果判断结果为存在的时候元素不一定存在,但是判断结果为不存在的时候则一定不存在。因此,布隆过滤器不适合那些对结果必须精准的应用场景。
其他问题
不支持计数,同一个元素可以多次插入,但效果和插入一次相同
由于错误率影响hash函数的数量,当hash函数越多,每次插入、查询需做的hash操作就越多
布隆过滤器适合的场景
区块链中使用布隆过滤器来加快钱包同步;以太坊使用布隆过滤器用于快速查询以太坊区块链的日志
数据库防止穿库,Google Bigtable,HBase 和 Cassandra 以及 Postgresql 使用BloomFilter来减少不存在的行或列的磁盘查找。避免代价高昂的磁盘查找会大大提高数据库查询操作的性能
判断用户是否阅读过某一个视频或者文章,类似抖音,刷过的视频往下滑动不再刷到,可能会导致一定的误判,但不会让用户看到重复的内容
网页爬虫对URL去重,采用布隆过滤器来对已经爬取过的URL进行存储,这样在进行下一次爬取的时候就可以判断出这个URL是否爬取过了
使用布隆过滤器来做黑名单过滤,针对不同的用户是否存入白名单或者黑名单,虽然有一定的误判,但是在一定程度上还是很好的解决问题
缓存击穿场景,一般判断用户是否在缓存中,如果存在则直接返回结果,不存在则查询数据库,如果来一波冷数据,会导致缓存大量击穿,造成雪崩效应,这时候可以用布隆过滤器当缓存的索引,只有在布隆过滤器中,才去查询缓存,如果没查询到则穿透到数据库查询。如果不在布隆过滤器中,则直接返回,会造成一定程度的误判
WEB拦截器,如果相同请求则拦截,防止重复被攻击。用户第一次请求,将请求参数放入布隆过滤器中,当第二次请求时,先判断请求参数是否被布隆过滤器命中。可以提高缓存命中率。Squid 网页代理缓存服务器在 cache digests 中就使用了布隆过滤器。
Google Chrome浏览器使用了布隆过滤器加速安全浏览服务
Google 著名的分布式数据库 Bigtable 使用了布隆过滤器来查找不存在的行或列,以减少磁盘查找的IO次数
Squid 网页代理缓存服务器在 cache digests 中使用了也布隆过滤器
Venti 文档存储系统也采用布隆过滤器来检测先前存储的数据
SPIN 模型检测器也使用布隆过滤器在大规模验证问题时跟踪可达状态空间
Google Chrome浏览器使用了布隆过滤器加速安全浏览服务
布隆过滤器原理
数据结构

增加元素

查询元素

删除元素
布隆过滤器是不能删除的,删除会出问题,比如上面添加元素的 bit 位 5 被两个变量的哈希值共同覆盖的情况下,一旦我们删除其中一个值。例如“xinlang”而将其置位 0,那么下次判断另一个值例如“baidu”是否存在的话,会直接返回 false,而实际上我们并没有删除它,这就导致了误判的问题。
如何使用布隆过滤器
- Goole开源Guava
- redis配置
- redission集成
