缓存穿透
缓存穿透是指查询一个不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存。这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。
穿透解决方案
- 布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先校验,不符合则丢弃,从而避免对底层查询系统的压力 - 缓存空对象
在存储不命中后,即便返回的空对象也将其缓存起来,同时设置一个过期时间,之后访问这个数据会从缓存中获取,保护后端数据源
但是这种方法存在两个问题:- 如果空值能够被缓存,意味着缓存需要更多的空间存储更多的键,因为其中可能有很多空值的键
- 即便对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间的不一致,对于保持一致性的业务可能有影响
缓存击穿
对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,后者则是很多key。
缓存在某个时间点过期的时候,恰好在这个时间间隔对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
击穿解决方案
- 设置热点数据永不过期
- 从redis上看,确实没有设置过期时间,这就保证了,不会出现热点key过期问题,也就是“物理”不过期。
- 从功能上看,如果不过期,那不就成静态的了吗?所以我们把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期
- 加互斥锁
分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,只能等待。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。 - 提前使用互斥锁
在value内部设置1个超时值(timeout1), timeout1比实际的memcache timeout(timeout2)小。当从cache读取到timeout1发现它已经过期时候,马上延长timeout1并重新设置到cache。然后再从数据库加载数据并设置到cache中。 - 资源保护
采用netflix的hystrix,可以做资源的隔离保护主线程池。
解决方案 | 优点 | 缺点 |
---|---|---|
简单分布式互斥锁(mutex key) | 思路简单。 保证一致性 | 代码复杂度增大。存在死锁的风险。存在线程池阻塞的风险 。 |
“提前”使用互斥锁 | 保证一致性 | 同上 |
不过期 | 异步构建缓存。不会阻塞线程池 | 不保证一致性 。代码复杂度增大(每个value都要维护一个timekey)。 占用一定的内存空间(每个value都要维护一个timekey)。 |
资源隔离组件hystrix | hystrix技术成熟,有效保证后端。 hystrix监控强大。 | 部分访问存在降级策略。 |
雪崩
缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。
雪崩解决方案
- Redis高可用
增设服务器,搭建集群 - 降级限流
在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。 - 数据预热
在正式部署前,将可能访问的数据先访问一遍,将大部分可能访问的数据加载在缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的失效时间,让缓存失效的时间点尽量均匀