美文网首页系统架构
Redis 缓存雪崩、缓存击穿、缓存穿透、缓存预热

Redis 缓存雪崩、缓存击穿、缓存穿透、缓存预热

作者: 大鱼炖海棠 | 来源:发表于2019-07-25 21:46 被阅读205次

    常规的缓存处理流程是:客户端向服务端请求数据 ,服务端先从缓存中 get 数据,缓存命中则直接返回结果,否则从 DB 中取,DB 获取到数据就去更新缓存并返回结果,否则返回空。

    这个流程放在本地环境研究学习还行,如果要部署到生产,就需要考虑缓存雪崩、缓存击穿和缓存穿透的致命问题!

    一、缓存雪崩

    缓存雪崩是指某一时刻发生了大规模的缓存失效,比如发生了 Redis 服务器宕机、缓存数据同时到期被删除这种情况,此时大量的请求直接转发到数据库,数据库一旦撑不住就会导致整个服务瘫痪。


    缓存雪崩

    解决方案:

    • 分析用户行为,制定策略为 key 设置不同的过期时间,尽量让缓存失效的时间均匀分布
    • 采用主从架构 + Sentinel 或者 Redis Cluster 实现HA,避免 Redis 单点故障
    • 设置本地缓存(ehcache) + 限流(hystrix)。如果某个目标服务调用慢或者有大量超时,此时,熔断该服务的调用,对于后续调用请求,不再继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。服务降级的最终目的是保证核心服务可用
    • 开启 Redis 持久化机制,服务重启后快速恢复缓存数据

    PS:对服务降级前需做系统梳理,比如:哪些业务是核心,哪些业务可以容许暂时不提供服务(利用静态页面替换)等,以及配合服务器核心指标,来设置整体预案,比如:
    1)一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
    2)警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;
    3)错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
    4)严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。

    二、缓存击穿

    对于一些设置了过期时间的热点 key,需要考虑缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里是针对某个 key 的缓存,前者则是很多 key。

    解决方案:

    • 使用互斥锁(mutex key):让一个线程构建 Redis 缓存,其他线程等待构建缓存的线程执行完,然后直接从缓存获取数据
    String get(String key) {
        String value = redis.get(key);
        if (value == null) {
            if (redis.setnx(key_max, "1")) {
                redis.expire(KEY_MUTEX, 3 * 60);
                value = pg.get(key);
                redis.set(key, value);
                redis.delete(KEY_MUTEX);
            } else {
                // 其他线程 50ms 后重试
                Thread.sleep(50);
                get(key);
            }
        }
    }
    
    • 设置 key 永不过期:永不过期既要保证 key 一直持有在缓存中,又要能对 key 的 value 进行及时的更新。实现的方式是不为 key 设置物理过期时间,即不通过redis.expire(key, timeout) 命令设置过期时间;但是要设置一个逻辑的过期时间,可以把 key 的过期时间拼接到 value 中,程序访问缓存时,从 value 中解析出过期时间并根据规则判定 key 是否需要更新

    上述两种方案对比:

    方案 优点 缺点
    使用互斥锁(mutex key) 保证一致性 程序复杂度高,存在死锁和线程阻塞的风险
    设置 key 永不过期 异步构建缓存,线程不会阻塞 无法保证一致性,代码复杂度增加,每个 key 占用的空间增加

    三、缓存穿透

    缓存穿透是指查询一个一定不存在的数据。出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义,也很容易被攻击者作为系统漏洞进行攻击。

    解决方案:

    • 由于请求的参数是不合法的(每次都请求不存在的数据),于是我们可以使用布隆过滤器(BloomFilter)或者压缩 filter 提前拦截,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个bitmap 过滤掉,从而缓解底层存储系统的查询压力
    • 接口层增加校验,如用户鉴权校验,id 做基础校验,id<=0 的直接拦截
    • 即便存储层查不到这个数据,也将返回的空对象设置到缓存里。下次再请求的时候,直接从缓存取到空对象返回,这种情况一般会将空对象设置一个较短的过期时间,这样可以防止攻击者反复用同一个 id 暴力攻击

    四、缓存预热

    缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统,避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题,用户直接查询事先被预热的缓存数据。

    相关文章

      网友评论

        本文标题:Redis 缓存雪崩、缓存击穿、缓存穿透、缓存预热

        本文链接:https://www.haomeiwen.com/subject/ghvkrctx.html