经典问题:缓存穿透/热点数据集中失效/雪崩/一致性问题

缓存系统一定程度上极大提升系统并发能力,但同样也增加系统的复杂度,下面针对缓存系统设计与使用中面临的常见问题展开。主要是针对热门问题:缓存穿透/热点数据集中失效/雪崩/一致性问题的总结及解决方案。

There are only two hard things in Computer Science: cache invalidation and naming things.           – Phil Karlton

计算机科学中有两件难事:缓存失效和命名 

缓存穿透

什么是缓存穿透?

缓存穿透是指访问不存在数据,从而绕过缓存,直取数据源(大量数据源读取操作)

穿透带来的问题?

大量的请求到数据库去查询,可能会导致你的数据库由于压力过大而宕掉。

解决办法?

1.缓存空值

思路:缓存中没有存储这些空数据的key,导致每次查询都到数据库去了,那么我们就可以为这些key对应的值设置为null 丢到缓存里面去,后面再出现查询这个key 的请求的时候,直接返回null 。

2. BloomFilter

BloomFilter 类似于一个hbase set 用来判断某个元素(key)是否存在于某个集合中。

这种方式在大数据场景应用比较多,比如 Hbase 中使用它去判断数据是否在磁盘上,还有在爬虫场景判断url 是否已经被爬取过。

这种方案可以加在第一种方案中,在缓存之前在加一层 BloomFilter (在查询的时候先去 BloomFilter 去查询 key 是否存在,如果不存在就直接返回,存在再走查缓存 -> 查 DB)。

小结

针对于一些恶意攻击,攻击带过来的大量key 是不存在的,那么我们采用第一种方案就会缓存大量不存在key的数据。此时我们采用第一种方案就不合适了,我们完全可以先对使用第二种方案进行过滤掉这些key。

针对这种key异常多、请求重复率比较低的数据,我们就没有必要进行缓存,使用第二种方案直接过滤掉。

而对于空数据的key有限的,重复率比较高的,我们则可以采用第一种方式进行缓存。

缓存击穿(热点数据集中失效)

什么是击穿(热点数据集中失效)?

在平常高并发的系统中,大量的请求同时查询一个 key 时,此时这个key正好失效了,就会导致大量的请求都打到数据库上面去。这种现象我们称为缓存击穿

会带来什么问题?

会造成某一时刻数据库请求量过大,压力剧增。

如何解决?

1. 设置不同的失效时间

为了避免这些热点的数据集中失效,那么我们在设置缓存过期时间的时候,我们让他们失效的时间错开。比如在一个基础的时间上加上或者减去一个范围内的随机值。

2. 互斥锁

多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个 互斥锁来锁住它。其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。

缓存雪崩

什么是缓存雪崩?

缓存雪崩是指缓存系统失效,导致大量请求同时进行数据回源,导致数据源压力骤增而崩溃。两种情况会导致此问题:

  1. 多个缓存数据同时失效
  2. 缓存系统崩溃

解决办法?

事前:使用集群缓存,保证缓存服务的高可用,这种方案就是在发生雪崩前对缓存集群实现高可用,如果是使用 Redis,可以使用 主从+哨兵 ,Redis Cluster 来避免 Redis 全盘崩溃的情况。

事中:ehcache本地缓存 + Hystrix限流&降级,避免MySQL被打死,使用 ehcache 本地缓存的目的也是考虑在 Redis Cluster 完全不可用的时候,ehcache 本地缓存还能够支撑一阵。使用 Hystrix进行限流 & 降级 ,比如一秒来了5000个请求,我们可以设置假设只能有一秒 2000个请求能通过这个组件,那么其他剩余的 3000 请求就会走限流逻辑。

事后:开启Redis持久化机制,尽快恢复缓存集群,一旦重启,就能从磁盘上自动加载数据恢复内存中的数据。

缓存更新与数据一致性

常见缓存更新策略:

  • 读操作:命中缓存则返回,无缓存则取回源数据,写缓存
  • 写操作:先删除缓存,再更新数据源

会带来什么问题?

读写并发的场景下先删缓存操作可能导致脏数据入缓存。比如:

  • 线程A 写操作:删除缓存
  • 线程B 读操作:无缓存则取回源数据(旧数据),回写缓存(此时缓存中为旧数据)
  • 线程A 写操作:更新数据源
  • 此时缓存数据不一致:缓存中为旧数据,数据源为新数据,出现缓存旧数据问题

解决办法?

1. Cache Aside Pattern:先数据源更新后,再失效缓存(由等待下次读取来回写缓存)

  • 优势:无缓存旧数据问题、缓存系统维护简单、Facebook推荐方案
  • 问题:无法绝对杜绝并发读写问题,这种问题出现概率极低,几点要求:缓存已过期、并发读写、读数据比写数据快、但读操作更新缓存比写操作失效缓存慢(也就是说写操作的行为需完全发生在读操作两步之间),一般而言读操作(读库+更新缓存)时长要小于写操作(更新数据源+失效缓存),所以认为这种并发问题概率较低。可以通过增加锁机制,解决并发问题。

2. Read Through Pattern:更新数据源由缓存系统操作

  • 读取数据时,如缓存失效,则缓存服务取回源数据更新缓存
  • 而Cache Aside中是由应用服务(调用方)更新缓存
  • 这套对调用方是透明的,只有一套存储系统,而无视缓存、数据源的差异

3. Write Through Pattern:更新数据源由缓存系统操作

  • 写数据时,如缓存失效,则直接更新数据源(不做任何缓存操作);如命中缓存,则更新缓存(由缓存系统更新数据源)
  • 在缓存失效下写操作的处理后,何时更新缓存呢?下一次读操作,按Read Through中缓存失效策略来更新缓存

4. Write Behind Caching Pattern:又称Write Back

一句话总结:更新数据时,只更新缓存,不更新数据源(缓存异步批量更新数据源)

优势:

  • 更新缓存为内存操作,读写I/O非常高
  • 异步批量更新数据源,合并多个操作

问题:

  • 缓存不满足强一致性要求
  • 强一致性和高性能的冲突高可用和高性能的冲突终究会使Trade-Off
  • 实现复杂,需跟踪哪些Cache更新,成本较高

一般而言,推荐Cache Aside Pattern方案,容忍较小概率的不一致(同时也可以增加锁机制解决此低概率并发问题),简化缓存系统复杂度。

参考资料

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值