1. 讲个故事

一个人去门店买联想电脑,线下门店没有货了,于是店员给厂家打电话问问有没有货,厂家发现也没货了,这个人就走了。过了一会另一个人也要来买联想电脑,然后店员又打电话问了一次厂家,如此反复。这种情况就是“缓存穿透”。

联想电脑太火了,所有门店都没货了,然而很多顾客都会来门店购买,那么就会有很多门店的很多店员都会给厂家打电话询问有没有货,导致厂家电话被打爆。这种情况就是“缓存击穿”。

不只是联想电脑火,华硕、戴尔也特别火,门店都卖光了,而又有很多人来买电脑,这时候又有很多门店同时打电话咨询厂家,或者因为门店人太多被挤爆了,很多客户自己打电话咨询厂家,厂家电话被打爆。这种情况就是“缓存雪崩”。


2. 一句话总结

2.1 缓存穿透

指缓存服务器中没有要查的数据,数据库中也没有,导致请求每次都绕过缓存服务器去查询下游数据库,缓存服务器失去了应有的作用,数据库访问压力很大。

2.2 缓存击穿

指的是某一个经常被访问的热key缓存过期的那一刻,大量请求访问这个key,会瞬间穿透缓存服务器同时访问数据库,导致数据库访问压力很大,出现过载的情况。

2.3 缓存雪崩

指缓存服务器大量缓存同时过期,或者缓存服务器宕机了,某一时刻所有的请求直接访问到数据库,造成数据库访问压力很大,出现过载,影响性能甚至宕机的情况。


3. 如何解决?

3.1 如何解决缓存穿透?

我们知道这种情况的出现很可能是遭到了恶意攻击,比如发起对id为-1或者很大的数据的访问,这样的数据明显是不会存在到数据库中的,所以也不会返回结果,存储到缓存数据库。这种情况有两种解决方案:

  1. 如果缓存中没有的数据,在数据库中也没有查到,那么我们可以返回value值为null,存储在缓存数据库,设置一个比较短的有效时间,例如30s。
  2. 使用布隆过滤器,过滤掉对id <= 0 或者id很大的访问。

3.2 如何解决缓存击穿?

分清缓存击穿和缓存穿透的区别,缓存穿透多数情况下数据库中是没有这条数据的,而缓存击穿时,数据库是有这条数据的,只是在缓存中过期了,但因为这条数据是热key,并发访问量非常大,所以会导致数据库过载击穿。
我们有如下三种解决方案:

  1. 设置热点key永不过期。
    这里并不是给这个数据的存活时间设置为永久,而是将过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的重建。
    热点key不过期

  2. 使用互斥锁。
    在单体项目中是这样的原理:数据的缓存过期后,只有首个访问的线程可以访问数据库,其他线程等待这个线程查询到数据,更新缓存后即可直接从缓存中获取数据。
    加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量。在高并发情况下,缓存重建期间key是锁着的,这是过来1000个请求999个都在阻塞的,同样会导致用户等待超时,这是个治标不治本的方法。所以在高并发场景下,尽可能不使用加锁的方式。
    以下是伪代码:

public static String getData(String key) throws InterruptedException {
    String result = getDataFromRedis(key);
    if (result == null) {
        if (lockObj.getLock()) {
            result = getDataFromMysql(key);
            if (result != null) {
                redis.set(key, result);
            }
            lockObj.unLock();
        }
    } else {
        Thread.sleep(100);
        result = getDataFromRedis(key);
    }
    return result;
}
  1. 定时刷新。
    这个和第一条大同小异,我们可以在后台写一个定时任务,假如这条数据的存活时间为10分钟,我们可以每9分钟执行一次定时任务,将数据库中查到的数据更新到缓存中,刷新存活时间。

3.3 如何解决缓存雪崩?

缓存雪崩就是缓存击穿的加强版,所以也可以通过加互斥锁的方式解决。同样,也不适用在高并发的场景下。除了这个,还有以下两种解决方案。

  1. 设置过期时间为随机值。
    我们给缓存设置一个随机的过期时间,使缓存的失效时间可以均匀分布,避免缓存在某一时间大量过期导致缓存雪崩。

  2. 设置一个缓存标记更新缓存。
    缓存标记,记录缓存数据是否过期,如果过期会触发另外的线程在后台去更新实际key的缓存。
    真正的缓存数据过期时间比缓存标记多一倍,当缓存标记key过期后,实际缓存还能把旧数据返回给调用端,直到另外的线程在后台更新完成后才会返回新数据。

Logo

欢迎加入西安开发者社区!我们致力于为西安地区的开发者提供学习、合作和成长的机会。参与我们的活动,与专家分享最新技术趋势,解决挑战,探索创新。加入我们,共同打造技术社区!

更多推荐