一、缓存雪崩
我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期。所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。
解决:
- 通过加锁或者队列来保证对某个key只允许一个线程查询数据,其他线程等待,从而避免失效时大量的并发请求落到数据库上。(不推荐,线程阻塞,用户体验差)
- 为key设置不同的缓存失效时间。将缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
- 定期刷新缓存。
- 做二级缓存,或者双缓存策略:Cache1 为原始缓存,Cache2 为拷贝缓存,Cache1 失效时,可以访问 Cache2,Cache1 缓存失效时间设置为短期,Cache2 设置为长期。
- 将热key分布在不同的redis上。
- 设置热点数据永不过期。
二、缓存击穿
缓存击穿是指缓存中的某个热点key缓存时间过期,由于并发用户特别多,同时缓存中读不到数据,都去数据库中去读数据,引起数据库压力瞬间增大。
解决:
- 设置热点数据永不过期
- 使用互斥机制,加锁,获取锁的才能读库。
三、缓存穿透
缓存穿透是指用户一个一定不存在的数据,在数据库没有,自然在缓存中也不会有。这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。
解决:
- 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
- 如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。通过这个直接设置的默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库。
缓存空对象带来的问题:
- 空值做了缓存,意味着缓存中存了更多的键,需要更多的内存空间,比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。
- 缓存和存储的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如:过期时间设置为 5分钟,如果此时存储添加了这个数据,那此段时间就会出现缓存和存储数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象。
四、缓存预热
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
解决:
- 直接写个缓存刷新页面,上线时手工操作下;
- 数据量不大,可以在项目启动的时候自动进行加载;
- 定时刷新缓存;
五、缓存更新
除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:
(1)定时去清理过期的缓存;
(2)当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。
两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!具体用哪种方案,大家可以根据自己的应用场景来权衡。
六、数据库缓存如何保证一致性
1.先操作缓存,再操作数据库
- 先更新缓存,再更新数据库,并发场景下可能会导致缓存和数据库不一致
- 线程1和线程2同时执行更新操作
- 线程1更新缓存
- 线程2更新缓存
- 线程2更新数据库
- 线程1更新数据库
- 先删除缓存,再更新数据库,并发场景下可能会导致缓存和数据库不一致
- 线程1执行更新操作,线程2执行读操作
- 线程1删除缓存
- 线程2读取缓存,发现缓存没有,然后从数据库中读取数据,并且放到缓存中。
- 线程1更新数据库。但是在缓存中的还是旧数据。
2.先操作数据库,再操作缓存
- 先更新数据库再更新缓存,并发场景下可能会导致数据不一致
- 线程1和线程2同时执行更新操作
- 线程1更新了数据库
- 线程2更新了数据库,并且写了缓存
- 线程1再更新缓存,数据库和缓存中的数据不一致了。
- 先更新数据库,再删除缓存,并发场景下也可能导致数据不一致,但是几率比较小。
- 线程1更新操作,线程2读操作
- 线程2先取缓存中读,缓存恰巧失效了,缓存中读取不到,就去数据库中读数据
- 线程1执行更新数据库删除缓存的操作。
- 线程2再将刚刚读到的数据放到缓存中。缓存中还是旧数据。
不过,实际上出现的概率可能非常低,因为这种情况发生需要两个条件
3. 读缓存时缓存失效,而且并发着有一个写操作。
4. 而读操作必需在写操作前进入数据库操作,而又晚于写操作更新缓存。而实际上数据库的写操作会比读操作慢得多,而且还要锁表,所有的这些条件都具备的概率基本并不大。
更新数据库成功,删除缓存失败怎么办
- 将想要删除的key放进了消息队列,
- 消息队列的消费端收到这个key的时候就执行删除缓存中对应key的操作
- 设置重试删除操作,超过最大重试次数(比如5次)后将就报警给运维人员