缓存穿透
概念
查询一个根本不存在的数据,缓存层和存储层都不会命中,但是出于容错的考虑,如果从存储层查不到数据则不写入缓存层。
缓存穿透将导致不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储的意义。
解决方案:
缓存空对象
需要过多的存储空间,数据不一致
布隆过滤拦截
代码维护复杂,存储空间小
缓存并发
概念
网站并发访问高,一个缓存如果失效,可能出现多个进程同时查询DB,同时设置缓存的情况,如果并发确实很大,这也可能造成DB压力过大,还有缓存频繁更新的问题。
解决方案:
对缓存查询加锁,如果KEY不存在,就加锁,然后查DB入缓存,然后解锁;
其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入DB查询。
会造成部分请求等待。
缓存雪崩
概念
数据未加载到缓存中,或者缓存同一时间大面积的失效,从而导致所有请求都去查数据库,导致数据库CPU和内存负载过高,甚至宕机
解决方案:
- 保证缓存层服务高可用
- 依赖隔离组件为后端限流并降级
具体的应用:
- 采用加锁计数,或者使用合理的队列数量来避免缓存失效时对数据库造成太大的压力。这种办法虽然能缓解数据库的压力,但是同时又降低了系统的吞吐量。(2)
- 分析用户行为,尽量让失效时间点均匀分布。避免缓存雪崩的出现。
- 如果是因为某台缓存服务器宕机,可以考虑做主备,比如:redis主备,但是双缓存涉及到更新事务的问题,update可能读到脏数据,需要好好解决。(1)
缓存热点
概念
开发人员使用缓存 + 过期时间的策略既可以加速数据读写,又保证数据的定期更新,这种模式基本能够满足绝大部分需求。但是有两个问题如果同时出现,可能就会对应用造成致命的危害:
- 当前 key 是一个热点 key( 例如一个热门的娱乐新闻),并发量非常大。
- 重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的 SQL、多次 IO、多个依赖等。
在缓存失效的瞬间,有大量线程来重建缓存 ( 如下图),造成后端负载加大,甚至可能会让应用崩溃。
解决方案:
-
互斥锁:只允许有一个线程去重建数据,其他线程等待构建完缓存,重新从缓存中获取数据。
直接加锁,这种方案思路比较简单,但是存在一定的隐患,如果构建缓存过程出现问题或者时间较长,可能会存在死锁和线程池阻塞的风险,但是这种方法能够较好的降低后端存储负载并在一致性上做的比较好。
-
永不过期:设置逻辑过期时间,判断逻辑时间和当前时间大小,然后异步去构建数据覆盖老数据
这种方案由于没有设置真正的过期时间,实际上已经不存在热点 key 产生的一系列危害,但是会存在数据不一致的情况,同时代码复杂度会增大。
记得net缓存有滑动时间及缓存重建callback