缓存:应用场景、常见问题和解决方案
缓存和数据库,数据不一致解决方案
- 最终一致性方案: 设置过期时间
- 实时一致性方案: canal binlog
并发问题
- 分布式锁 : redis zookeeper
缓存应用场景
- 访问量大,QPS高,更新频率不是很高的业务
- 数据一致性要求不高
常见的缓存问题和解决方案
缓存击穿问题(热点数据单个key)
对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,后者则是很多key。
![image-20230403182657251](https://material-li.oss-cn-beijing.aliyuncs.com/img/image-20230403182657251.png)
解决方案:
1.加锁,在未命中缓存时,通过加锁避免大量请求访问数据库
2.不允许过期。物理不过期,也就是不设置过期时间。而是逻辑上定时在后台异步的更新数据。
3.采用二级缓存。L1缓存失效时间短,L2缓存失效时间长。请求优先从L1缓存获取数据,如果未命中,则加锁,保证只有一个线程去数据库中读取数据然后再更新到L1和L2中。然后其他线程依然在L2缓存获取数据。
缓存穿透问题(恶意攻击、访问不存在数据):
缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。
![image-20230403182846167](https://material-li.oss-cn-beijing.aliyuncs.com/img/image-20230403182846167.png)
解决方案:有很多种方法可以有效地解决缓存穿透问题
1、最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
2、空结果缓存:另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
![image-20230403182934041](https://material-li.oss-cn-beijing.aliyuncs.com/img/image-20230403182934041.png)
缓存雪崩(同一时间失效,并发量大):
存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。
![image-20230403183015630](https://material-li.oss-cn-beijing.aliyuncs.com/img/image-20230403183015630.png)
解决方案:
1、 缓存失效分散,缓存失效时的雪崩效应对底层系统的冲击非常可怕。大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线 程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。这里分享一个简单方案就时讲缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
2、事前:这种方案就是在发生雪崩前对缓存集群实现高可用,如果是使用 Redis,可以使用 主从+哨兵 ,Redis Cluster 来避免 Redis 全盘崩溃的情况。
3、事中:使用 Hystrix进行限流 & 降级 ,比如一秒来了5000个请求,我们可以设置假设只能有一秒 2000个请求能通过这个组件,那么其他剩余的 3000 请求就会走限流逻辑。然后去调用我们自己开发的降级组件(降级),比如设置的一些默认值呀之类的。以此来保护最后的MySQL 不会被大量的请求给打死。
4、事后:开启Redis持久化机制,尽快恢复缓存集群
缓存和数据库双写一致性问题
一致性问题是分布式常见问题,还可以再分为最终一致性和强一致性。数据库和缓存双写,就必然会存在不一致的问题。
最终一致性方案,无法完全避免不一致发生的概率,不能放置有强一致性要求的数据。