由于原有缓存失效,新缓存未到期间,所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。
大多数系统设计者考虑用加锁( 最多的解决方案)或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。
还有一个简单方案就时讲缓存失效时间分散开。在批量往Redis存数据的时候,把每个Key的失效时间都加个随机值就好了,这样可以保证数据不会在同一时间大面积失效了!
缓存击穿是指一个Key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个Key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个完好无损的桶上凿开了一个洞。
就是这个值是数据库新增的,但是缓存中暂时还没有,这个时候刚好并发请求进来了,如果处理不当也会发生
尽量少的线程构建缓存(甚至是一个) + 数据一致性 + 较少的潜在危险。有四种方法来解决这个问题:
1、使用互斥锁(mutex key)(业界比较常用)
2、"提前"使用互斥锁(mutex key)
3、"永远不过期"
4、资源保护:采用netflix的hystrix,可以做资源的隔离保护主线程池,如果把这个应用到缓存的构建也未尝不可。
作为一个并发量较大的互联网应用,我们的目标有3个:(没有最好,只有最合适)
解决方案 | 优点 | 缺点 |
---|---|---|
简单分布式锁(Tim yang) | 1. 思路简单 2. 保证一致性 |
1. 代码复杂度增大 2. 存在死锁的风险 3. 存在线程池阻塞的风险 |
加另外一个过期时间(Tim yang) | 保证一致性 | 同上 |
不过期(本文) | 异步构建缓存,不会阻塞线程池 | 1. 不保证一致性。 2. 代码复杂度增大(每个value都要维护一个timekey)。 3. 占用一定的内存空间(每个value都要维护一个timekey)。 |
资源隔离组件hystrix(本文) | 1. hystrix技术成熟,有效保证后端。 2. hystrix监控强大。 |
部分访问存在降级策略。 |
请求的数据在缓存中找不到,每次都要去数据库再查询一遍也返回空(相当于进行了两次无用的查询)。
这样请求就绕过缓存直接查数据库,失去了缓存的意义,这也是经常提的缓存命中率问题。
最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。