什么是缓存穿透,缓存击穿和缓存雪崩

  1. 缓存穿透:缓存和数据库中都没有的数据,被大量请求(速记:缓存和数据库都不存在)
  2. 缓存击穿:缓存中的数据突然失效时,正好数据被大量请求到,此时会去数据库中请求该数据(速记:缓存数据不存在,大量请求同时抵达)
  3. 缓存雪崩:同一时间,系统中存在大量的缓存全部过期,导致大量请求打到数据库,导致数据库压力激增,从而引起全局服务问题(速记:大量缓存全部过期,请求直接打到数据库)

解决方案

缓存穿透

解决方案1:添加[[布隆过滤器]],针对不存在的数据可以根据业务返回对应的结果 解决方案2:针对缓存和数据库都没有的数据,可以在缓存层面设置一个过期时间较短的值(过期时间=5min),用于返回前台

缓存击穿

解决方案1. 如果是热点数据,那么可以考虑设置永远不过期。 解决方案2. 如果数据一定会过期,那么就需要在数据为空的时候,设置一个互斥锁,只让一个请求通过,该请求去数据库拉取数据,取完数据,不管如何都需要释放锁,异常的时候也需要释放锁,要不其他线程会一直拿不到锁。

下面是缓存击穿的时候互斥锁的写法,注意:获取锁之后操作,不管成功或者失败,都应该释放锁,而其他的请求,如果没有获取到锁,应该等待,再重试。当然,如果是需要更加全面一点,应该加上一个等待次数,比如1s中,那么也就是睡眠五次,达到这个阈值,则直接返回空,不应该过度消耗机器,以免当个不可用的场景把整个应用的服务器带挂了。

    public static String getProductDescById(String id) {
        String desc = redis.get(id);
        // 缓存为空,过期了
        if (desc == null) {
            // 互斥锁,只有一个请求可以成功
            if (redis.setnx(lock_id, 1, 60) == 1) {
                try {
                    // 从数据库取出数据
                    desc = getFromDB(id);
                    redis.set(id, desc, 60 * 60 * 24);
                } catch (Exception ex) {
                    LogHelper.error(ex);
                } finally {
                    // 确保最后删除,释放锁
                    redis.del(lock_id);
                    return desc;
                }
            } else {
                // 否则睡眠200ms,接着获取锁
                Thread.sleep(200);
                return getProductDescById(id);
            }
        }
    }

缓存雪崩

  1. 如果是热点数据,那么可以考虑设置永远不过期。
  2. 缓存的过期时间除非比较严格,要不考虑设置一个波动随机值,比如理论十分钟,那这类key的缓存时间都加上一个1~3分钟,过期时间在7~13分钟内波动,有效防止都在同一个时间点上大量过期。
  3. 方法1避免了有效过期的情况,但是要是所有的热点数据在一台redis服务器上,也是极其危险的,如果网络有问题,或者redis服务器挂了,那么所有的热点数据也会雪崩(查询不到),因此将热点数据打散分不到不同的机房中,也可以有效减少这种情况。
  4. 也可以考虑双缓存的方式,数据库数据同步到缓存A和B,A设置过期时间,B不设置过期时间,如果A为空的时候去读B,同时异步去更新缓存,但是更新的时候需要同时更新两个缓存。

比如设置产品的缓存时间:redis.set(id,value,60*60 + Math.random()*1000);

小结

缓存穿透是指数据库原本就没有的数据,请求如入无人之境,直奔数据库,而缓存击穿,则是指数据库有数据,缓存也本应该有数据,但是突然缓存过期了,这层保护屏障被击穿了,请求直奔数据库,缓存雪崩则是指很多缓存同一个时间失效了,流量全部涌入数据库,造成数据库极大的压力。