这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战
-
缓存雪崩
-
数据库每秒 1000的并发是⼀个正常的指标,一旦缓存宕机,过期时间一致导致缓存同时失效。请求打到数据库,数据库就崩了。重启数据库,⽴⻢⼜会被新的请求打死了。
-
解决
-
事前:redis⾼可⽤,主从+哨兵,redis cluster,避免全盘崩溃
-
事中:本地ehcache缓存 + hystrix限流&降级,避免MySQL被打死
-
事后:redis持久化RDB+AOF,快速恢复缓存数据
-
缓存的失效时间设置为随机值,避免同时失效
-
-
-
缓存穿透
-
黑客的恶意行为,大量的请求一个缓存不存在的数据,都打在服务器上。数据库很快被打死。
-
解决
-
对请求参数进⾏校验,不合理直接返回
-
查询不到的数据也放到缓存,value为空,如 set -999 ""(不推荐----如果黑客使用的是随机数请求,那么无法方式,还占了内存引发缓存清空策略导致有用的缓存被清空)
-
使⽤布隆过滤器,快速判断key是否在数据库中存在,不存在直接返回(常用)
-
-
-
缓存击穿
-
热点数据,并发量很高,在失效的时候,重新拉数据库到缓存的期间,大量的请求导致数据库打死。
-
缓存雪崩是指⼤量缓存失效,缓存击穿是指热点数据的缓存失效
-
解决
-
设置key永远不过期,或者快过期时,通过另⼀个异步线程重新设置key
-
当从缓存拿到的数据为null,重新从数据库加载数据的过程上锁,下⾯写个分布式锁实现的demo
-
-
-
缓存数据库双写一致性
-
除了加锁让双写操作处于一个事务中可以满足强一致性,其他的所有方案其实都是弱一致性,但是要求最后能达到最终一致性。
-
我们一般就是采用cache-aside模式来缓存操作的:即 先更新数据库,然后删除缓存。(简单并且出错概率小)。读:先读缓存,再读数据库。
-
为什么不先删除缓存
- 如果删除了缓存,还没写数据库,那么其他线程请求数据就会将历史数据再次刷新到缓存内。
-
为什么不更新缓存
-
所有的方案都是直接删缓存的!因为更新缓存在高并发的场景下很可能会出现脏读的情况。
-
2个线程A和B。A先更新数据库,但是最后更新的缓存。此时缓存应该是B的值,但是因为网络原因会导致最终是A的。
-
如图
-
-
-
cache-aside依然存在的一些问题
-
数据库做了读写分离
-
写库操作完成了,删除了缓存,但是此时还没有同步到读库,被另外线程去读取了,就会导致读到脏数据
-
解决:延迟再删除!设置延迟时间大于主从同步时间即可。(但是同步进行延迟删除,可能会浪费系统资源,所以有必要的话做异步处理:1. 可以将删除操作丢到队列,然后消费队列进行删除,如果删除失败就再次丢到队列。2. 也可以监听binlog来异步删除)
-
-
数据库更新成功,缓存更新失败!
- 通过重试机制,如果重试多次都失败,说明redis可能故障,此时应该将该key记录下来,并及时报警排查处理。
-
-
如果一定要先删缓存再更新DB呢?(这指定是有毛病)
-
直接这么做肯定是不行的,但是这个可以通过延迟双删解决!(注意延迟双删只是为了非要先删缓存场景设计的)
-
延迟双删,就是先删除缓存,然后更新数据库后,等待一段时间后再删除缓存。
-
如果嫌等待时间长会降低系统吞吐量,我们延迟操作异步去做(开一个异步线程,或者消息队列,或者binlog方式监听延迟删除等方式)。
-
-
-
严格要求缓存和数据库双写一致性,只能加锁来串行化,写的时候不让其他的线程来读!
-