10 分布式缓存可能出现的问题

一、缓存雪崩

我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期。所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。
解决:

  1. 通过加锁或者队列来保证对某个key只允许一个线程查询数据,其他线程等待,从而避免失效时大量的并发请求落到数据库上。(不推荐,线程阻塞,用户体验差)
  2. 为key设置不同的缓存失效时间。将缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
  3. 定期刷新缓存。
  4. 做二级缓存,或者双缓存策略:Cache1 为原始缓存,Cache2 为拷贝缓存,Cache1 失效时,可以访问 Cache2,Cache1 缓存失效时间设置为短期,Cache2 设置为长期。
  5. 将热key分布在不同的redis上。
  6. 设置热点数据永不过期。
二、缓存击穿

缓存击穿是指缓存中的某个热点key缓存时间过期,由于并发用户特别多,同时缓存中读不到数据,都去数据库中去读数据,引起数据库压力瞬间增大。

解决:

  1. 设置热点数据永不过期
  2. 使用互斥机制,加锁,获取锁的才能读库。
三、缓存穿透

缓存穿透是指用户一个一定不存在的数据,在数据库没有,自然在缓存中也不会有。这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。
解决:

  1. 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
  2. 如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。通过这个直接设置的默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库。
缓存空对象带来的问题:
  1. 空值做了缓存,意味着缓存中存了更多的键,需要更多的内存空间,比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。
  2. 缓存和存储的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如:过期时间设置为 5分钟,如果此时存储添加了这个数据,那此段时间就会出现缓存和存储数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象。
四、缓存预热

缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
解决:

  1. 直接写个缓存刷新页面,上线时手工操作下;
  2. 数据量不大,可以在项目启动的时候自动进行加载;
  3. 定时刷新缓存;
五、缓存更新

除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:

(1)定时去清理过期的缓存;

(2)当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。

两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!具体用哪种方案,大家可以根据自己的应用场景来权衡。

六、数据库缓存如何保证一致性
1.先操作缓存,再操作数据库
  1. 先更新缓存,再更新数据库,并发场景下可能会导致缓存和数据库不一致
    • 线程1和线程2同时执行更新操作
    • 线程1更新缓存
    • 线程2更新缓存
    • 线程2更新数据库
    • 线程1更新数据库
  2. 先删除缓存,再更新数据库,并发场景下可能会导致缓存和数据库不一致
    • 线程1执行更新操作,线程2执行读操作
    • 线程1删除缓存
    • 线程2读取缓存,发现缓存没有,然后从数据库中读取数据,并且放到缓存中。
    • 线程1更新数据库。但是在缓存中的还是旧数据。
2.先操作数据库,再操作缓存
  1. 先更新数据库再更新缓存,并发场景下可能会导致数据不一致
    • 线程1和线程2同时执行更新操作
    • 线程1更新了数据库
    • 线程2更新了数据库,并且写了缓存
    • 线程1再更新缓存,数据库和缓存中的数据不一致了。
  2. 先更新数据库,再删除缓存,并发场景下也可能导致数据不一致,但是几率比较小。
    • 线程1更新操作,线程2读操作
    • 线程2先取缓存中读,缓存恰巧失效了,缓存中读取不到,就去数据库中读数据
    • 线程1执行更新数据库删除缓存的操作。
    • 线程2再将刚刚读到的数据放到缓存中。缓存中还是旧数据。

不过,实际上出现的概率可能非常低,因为这种情况发生需要两个条件
3. 读缓存时缓存失效,而且并发着有一个写操作。
4. 而读操作必需在写操作前进入数据库操作,而又晚于写操作更新缓存。而实际上数据库的写操作会比读操作慢得多,而且还要锁表,所有的这些条件都具备的概率基本并不大。

更新数据库成功,删除缓存失败怎么办

  1. 将想要删除的key放进了消息队列,
  2. 消息队列的消费端收到这个key的时候就执行删除缓存中对应key的操作
  3. 设置重试删除操作,超过最大重试次数(比如5次)后将就报警给运维人员

另外缓存有效期不要设置过长。
还可以设置定时任务刷新缓存。
参考
消息队列的重试机制

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值