14.6缓存异常
四个方面
- 缓存中数据和数据库不一致
- 缓存雪崩
- 缓存击穿
- 缓存穿透
14.6.1数据不一致:
一致性包括两种情况
- 缓存中有数据,需要和数据库值相同
- 缓存中没有数据,数据库中的数据是最新值
如果不符合以上两种情况,则出现数据不一致的问题。
读写缓存
- 同步直写
- 异步写回
只读缓存
- 新增数据
- 数据直接写到数据库中,缓存不做操作。满足一致性两种情况的第2种。
- 删改数据
- 先删除缓存,后更新数据库。可能会导致,缓存删除成功,数据库更新失败。业务逻辑去访问数据时,缓存中查不到数据,缓存缺失,到数据库中查询,所以只拿到旧的数据。
- 如果新更新数据库,再删除缓存。可能会导致,数据库更新成功,缓存删除失败。数据库中的数据是新的值,缓存中存储的是旧值。再读取时,先从缓存中读取,读取到了旧值。
解决数据不一致的方案
- 重试机制:把删除的缓存值或要更新数据库值先存储到消息队列中(kafka消息队列)。
- 如果发现试了10次还不成功就会向服务器端报错
多线程访问的情况
-
先删除缓存,再更新数据库。
-
假设T1线程先删除缓存,再执行更新数据库。还未更新成功时,T2线程进行读取,发现缓存中没有数据,到数据库中读取,会读取到旧的数据。如果T2还将旧数据更新到缓存中,那T1线程再进行读取,也读到的旧值。
-
让T1线程先执行休眠一段时间。T1线程在休眠时间,让T2线程执行结束,会将数据重新写入缓存。T1线程再做一次缓存删除操作。"延迟双删 "。
javaredis.delCache() db.update() Thread.sleep(2000) redis.delCache()
-
-
先更新数据库值,再去删除缓存
- 假设T1线程先删除或更新数据库中的值,还没来得及删除缓存时,T2线程就开始读取数据。T2会先从缓存中读取,缓存命中,T2拿到的就是旧的数据。直到T1将缓存中数据删除,其他线程再次读取,可以拿到新值.
并发操作 | 执行顺序 | 可能出现问题 | 问题描述 | 解决方案 |
---|---|---|---|---|
没有 | 先删除缓存,后更新数据库 | 缓存删除成功,数据库更新失败 | 从数据库读到旧数据 | 重试(通过消息队列) |
有 | 先删除缓存,后更新数据库 | 缓存删除,未更新数据库,其他线程并发访问 | 并发线程从数据库读到旧值,并更新了缓存,其他线程都从缓存中读到旧值 | 延迟双删 |
没有 | 先更新数据库,后删除缓存 | 数据库更新成功,缓存删除失败 | 从缓存中读到旧值 | 重试(通过消息队列) |
有 | 先更新数据库,后删除缓存 | 数据库更新成功,未删除缓存,其他同线程并发访问 | 并发线程从缓存读到旧值 | 会有数据不一致情况短暂存在 |
14.6.2 缓存雪崩
大量的应用请求无法在redis中完成处理。缓存中读取不到数据,直接进入到数据库服务器。数据库压力激增,数据库崩溃,请求堆积在redis,导致redis服务器崩溃,导致redis集群崩溃,应用服务器崩溃,称为雪崩
原因1:缓存中有大量数据同时过期
解决方案:
- 页面静态化处理数据:对于不经常更换的数据,生成静态页
- 避免大量数据同时过期:为商品过期时间追加一个随机数,在一个较小的范围内(1~3分钟)。
- 构建多级缓存架构:redis缓存+nginx缓存+ehcache缓存
- 延长或取消热度超高的数据过期时间
- 服务降级
不同的数据采取不同的处理方式。
原因2:redis实例故障
解决方案:
- 服务熔断或限流处理
提前预防:
- 灾难预警:监控redis服务器性能指标,包括数据库服务器性能指标,CPU、内存、平均响应时间、线程数等
- 集群:有节点出一故障,主从切换。
14.6.2 缓存击穿
对某个访问频繁热点数据的请求。主要发生在热点数据失效
解决方案:
- 预先设定:电商双11,商铺设定几款是主打商品,延长过期时间
- 实时监控:监控访问量,避免访问量激增
- 定时任务:启动任务调度器,后台刷新数据有效期
- 分布式锁:可防止缓存击穿,但会有性能问题 (不推荐)
14.6.3 缓存穿透
要访问的数据在redis中不存在,在数据库中也不存在。
原因:
- 业务层误操作
- 恶意攻击
解决方案:
- 缓存空值或缺省值
- 使用布隆过滤器,快速判断数据是否存在
- 在请求入口前端进行请求检测
- 实时监控
- key加密