1. 缓存穿透
- 描述:key对应的数据在后端DB并不存在,每次针对该key的请求从Redis缓存获取不到,请求都会压到后端DB,从而可能压垮后端DB。
- 原因举例:黑客url访问攻击
- 解决方案:
- 对空值缓存:如果一个查询返回数据为空,我们仍然对该空结果进行缓存,并且设置空结果过期时间在5分钟内
- 设置白名单:使用bitmaps类型定义可访问的名单,名单id为bitmaps的偏移量,每次访问和bitmaps中id比较,如果不在bitmaps中,进行拦截
- 采用布隆过滤器:使用bf.add添加与bf.exists判断是否存在拦截,也可实现,但是会有一定的误判
- 实时监控:当运维系统检测到redis的命中率开始急速降低,需排查设置黑名单限制服务
2. 缓存击穿
- 描述:key对应的数据在后端DB存在,但在redis中过期,此时若大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这时高并发请求可能瞬间把后端DB压垮
- 解决方案:
- 预先设置热点数据:在redis高峰访问之前,把一些热门数据提前存入到redis里面,并且加大热点数据key时长
- 实时调整:现场监控哪些热门数据,实时调整key的过期时长
- 使用分布式锁:缓存key失效时,不立即load db, 加锁成功后,在load db,并回设到缓存,最后释放锁。这样其他并发请求后续会从缓存中获取数据
3. 缓存雪崩
- 描述:在某一时间段,缓存集中失效,导致请求全部走数据库,这时高并发请求可能瞬间把后端DB压垮
- 原因举例:
- redis服务器宕机
- 对缓存数据设置了相同的过期时间,导致某时间段内缓存集中失效
- 解决方案:
- 针对redis服务器宕机,可以实现redis的高可用:Redis Cluster(集群模式)或Redis Sentinel(哨兵模式)
- 将缓存失效时间分散开:设置缓存过期时间时加上一个随机值
- 构建多级缓存架构:nginx缓存+redis缓存+其他缓存
- 锁和队列控制:使用锁和队列来保证不会有大量线程对数据库一次性读写,不适宜高并发
- 设置过期标志更新缓存:记录缓存数据是否将要过期(设置提前量),如果将要过期会触发通知另外线程去更新实际key的缓存
缓存击穿与缓存雪崩的区别是击穿针对的是某一热门key缓存,而雪崩针对的是大量缓存的集中失效
4. 缓存预热
预先将热点数据加载到缓存系统中,这样系统上线后,用户访问会优先缓存查询,降低数据库查询压力
5. 缓存更新
缓存更新存在四种经典的模式:Cache Aside、Read Through、Write Through、Write Behind Caching
5.1 Cache Aside
这是最常用的模式
- 更新时,先把数据存到数据库中,成功后,再让缓存失效
- 查询时,如果命中缓存则返回,如果未命中,则把数据读出来后放到缓存中返回
5.2 Read Through
在查询操作中更新缓存。当缓存失效(过期或LRU换出)时,Cache Aside由调用方负责把数据载入缓存,而Read Through由Cache自己来加载,对应用方透明
5.3 Write Through
在更新操作中更新缓存。当数据更新时,如果未命中缓存,则直接更新数据库,然后直接返回,如果命中缓存,则更新缓存,Cache自己同步更新数据库
5.4 Write Behind Caching
Write Behind又叫Write Back,是Linux文件系统的Page Cache的算法。
Write Behind在更新数据的时候,只更新缓存,不更新数据库,而缓存会异步批量更新数据库
ps:先删除缓存,后更新数据库,再把数据装载至缓存存在的问题
试想,两个并发操作,一个更新操作,另一个查询操作,更新操作删除缓存后,查询操作没有命中缓存,先把老数据读出来后放到缓存中,然后更新操作更新了数据库。于是,缓存中的数据是老的脏数据,并且会一直脏下去
6.缓存降级
当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务,这时就需要降级,防止redis服务故障导致缓存雪崩。如双十一服务降级,地址无法修改仅允许默认地址下单