一:缓存遇到的一些问题
我们使用缓存的目的是为了提高查询的速度,给用户一个好的服务体验。但是缓存中一般会出现3个最常见的问题
1. 缓存穿透 2. 缓存击穿 3. 缓存雪崩
二:概念说明
1. 什么是缓存穿透
在高并发应用中,查询一个不存在的值时,这时候缓存没有命中,会导致大量请求直接打到了后端DB上,
由于DB承载的并发数有限,DB可能被压垮,导致不能提供服务
2. 什么是缓存击穿
在高并发应用中,对一个值进行查询时,但是这个值的缓存时间正好过期了,缓存没有命中,会导致大量请求直接打到后端DB上,发生和上面 "缓存穿透" 一样的情况
3. 缓存雪崩
在高并发应用中,大量缓存的key在同一时间失效,这样缓存也没有命中,就会导致大量请求直接打到后端DB上,导致DB压力剧增,最后肯能压垮而提供不了服务
三:解决方法
对于上面发生的几种情况,有下面的解决方法:
- 存储一个默认值,比如 &&
- 限流
- 缓存预热
- 分级缓存
- 加锁
- 布隆过滤器 bloomFilter
- 缓存永不过期
1. 存储默认值
这个方法既可以用在缓存穿透,也可以用在缓存击穿上
缺点:就是短时内这个数据或者服务不可用。
同时必须在这个时间内,通知后端把缓存的数据加载到缓存服务器上
2. 限流
限流也是一个很好应对缓存问题的一个方法
3.缓存预热
a) 主动更新缓存中的数据,可以定义一个后台缓存更新的任务,在缓存快要过期时候,主动更新缓存。
这种方案比较容易实现,
缺点:只适应那些key的过期时间相对固定, 如果key比较分散则不合适
b)还有一个惰性更新,就是每次 get 数据的时候,发现缓存快要过期了,更新这个数据。
缺点:如果这个数据一直没有get到达,那么就一直不能更新,突然在数据过期后有大量的get请求到达,那么就会悲剧了
4:分级缓存
采用 L1 (一级缓存)和 L2(二级缓存) 缓存方式,L1 缓存失效时间短,L2 缓存失效时间长。 请求优先从 L1 缓存获取数据,如果 L1缓存未命中则加锁,只有 1 个线程获取到锁,这个线程再从数据库中读取数据并将数据再更新到到 L1 缓存和 L2 缓存中,而其他线程依旧从 L2 缓存获取数据并返回。
这种通过避免缓存同时失效并结合锁机制实现。所以,当数据更 新时,只能淘汰 L1 缓存,不能同时将 L1 和 L2 中的缓存同时淘汰
缺点:L2 缓存中 可能会存在脏数据,需要业务能够容忍这种短时间的不一致。而且,这种方案
可能会造成额外的缓存空间浪费。
5:加锁
当多个请求达到达缓存时,缓存失效了,那么第一个到达的请求去数据库拿数据并发更新缓存,并锁定其它的请求,第一个数据更新了缓存解锁,其余数据从缓存拿数据
6:布隆过滤器
Bloom Filter(BF)是一种空间效率很高的随机数据结构,它利用位数组很简洁地表示一个集合,并能判断一个元素是否属于这个集合。它是一个判断元素是否存在集合的快速的概率算法
它的优点是空间效率和查询时间都远远超过一般的算法,
缺点:有一定的误识别率和删除困难
7:缓存永不过期
缓存永不过期意思:正在缓存过期时间不由Redis控制,而是由程序控制,当获取数据时发现数据超时时,就需要发起一个异步请求去加载数据
缺点:有可能会造成缓存不一致的现象, 和一定程度的空间浪费
参考:https://www.cnblogs.com/yeyinfu/p/7316978.html
https://blog.csdn.net/bushanyantanzhe/article/details/79459095