您当前的位置: 首页 >  缓存

庄小焱

暂无认证

  • 1浏览

    0关注

    805博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

Redis——缓存失效解决方案

庄小焱 发布时间:2021-03-16 10:56:46 ,浏览量:1

摘要

本博文将详细的介绍redis的作为缓存中,存在缓存失效的问题和解决方案。

一、缓存雪崩

缓存雪崩是指在短时间内,有大量缓存同时过期,导致大量的请求直接查询数据库,从而对数据库造成了巨大的压力,严重情况下可能会导致数据库宕机的情况叫做缓存雪崩。

我们先来看下正常情况下和缓存雪崩时程序的执行流程图,正常情况下系统的执行流程如下图所示:

缓存雪崩的执行流程,如下图所示:

1.1 解决方案

缓存雪崩的常用解决方案有以下几个。

1.1.1 加锁排队:

加锁排队可以起到缓冲的作用,防止大量的请求同时操作数据库,但它的缺点是增加了系统的响应时间,降低了系统的吞吐量,牺牲了一部分用户体验。

// 缓存 key
String cacheKey = "userlist";
// 查询缓存
String data = jedis.get(cacheKey);
if (StringUtils.isNotBlank(data)) {
    // 查询到数据,直接返回结果
    return data;
} else {
    // 先排队查询数据库,在放入缓存
    synchronized (cacheKey) {                // 使用加锁的机制来实现
        data = jedis.get(cacheKey);
        if (!StringUtils.isNotBlank(data)) { // 双重判断
            // 查询数据库
            data = findUserInfo();
            // 放入缓存
            jedis.set(cacheKey, data);
        }
        return data;
    }
}
1.1.2 随机化过期时间

为了避免缓存同时过期,可在设置缓存时添加随机时间,这样就可以极大的避免大量的缓存同时失效。

// 缓存原本的失效时间
int exTime = 10 * 60;

// 随机数生成类
Random random = new Random();

// 缓存设置
jedis.setex(cacheKey, exTime+random.nextInt(1000) , value);
1.1.3 设置二级缓存

二级缓存指的是除了 Redis 本身的缓存,再设置一层缓存,当 Redis 失效之后,先去查询二级缓存。例如可以设置一个本地缓存,在 Redis 缓存失效的时候先去查询本地缓存而非查询数据库。加入二级缓存之后程序执行流程,如下图所示:

 1.1.4 及时更新换缓存

   给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存。

二、缓存穿透

缓存穿透是指查询数据库和缓存都无数据,因为数据库查询无数据,出于容错考虑,不会将结果保存到缓存中,因此每次请求都会去查询数据库,这种情况就叫做缓存穿透。缓存穿透执行流程如下图所示:

其中红色路径表示缓存穿透的执行路径,可以看出缓存穿透会给数据库造成很大的压力。缓存穿透的解决方案有以下几个。

2.1 解决方案 2.1.1 鉴权校验

接口层增加校验(限流操作),如用户鉴权校验(token方法),id做基础校验,直接拦截;

2.1.2 设置缓存有效时间

从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击。

2.1.3 采用布隆过滤器

采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力。对于空间的利用到达了一种极致,那就是Bitmap和布隆过滤器(Bloom Filter)。Bitmap: 典型的就是哈希表缺点是,Bitmap对于每个元素只能记录1bit信息,如果还想完成额外的功能,恐怕只能靠牺牲更多的空间、时间来完成了。

布隆过滤器(推荐):就是引入了k(k>1)k(k>1)个相互独立的哈希函数,保证在给定的空间、误判率下,完成元素判重的过程。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。Bloom-Filter算法的核心思想就是利用多个不同的Hash函数来解决“冲突”。Hash存在一个冲突(碰撞)的问题,用同一个Hash得到的两个URL的值有可能相同。为了减少冲突,我们可以多引入几个Hash,如果通过其中的一个Hash值我们得出某元素不在集合中,那么该元素肯定不在集合中。只有在所有的Hash函数告诉我们该元素在集合中时,才能确定该元素存在于集合中。这便是Bloom-Filter的基本思想。Bloom-Filter一般用于在大数据量的集合中判定某元素是否存在。

三、缓存击穿

缓存击穿指的是某个热点缓存,在某一时刻恰好失效了,然后此时刚好有大量的并发请求,此时这些请求将会给数据库造成巨大的压力,这种情况就叫做缓存击穿。

3.1 解决方案 3.1.1 设置热点数据永远不过期

对于某些热点缓存,我们可以设置永不过期,这样就能保证缓存的稳定性,但需要注意在数据更改之后,要及时更新此热点缓存,不然就会造成查询结果的误差。

3.1.2 加互斥锁,互斥锁

此处理方式和缓存雪崩加锁排队的方法类似,都是在查询数据库时加锁排队,缓冲操作请求以此来减少服务器的运行压力

四、缓存预热

首先来说,缓存预热并不是一个问题,而是使用缓存时的一个优化方案,它可以提高前台用户的使用体验。缓存预热指的是在系统启动的时候,先把查询结果预存到缓存中,以便用户后面查询时可以直接从缓存中读取,以节约用户的等待时间。缓存预热的执行流程,如下图所示:

4.1 解决方案 4.1.1 启动自加载

把需要缓存的方法写在系统初始化的方法中,这样系统在启动的时候就会自动的加载数据并缓存数据;

4.1.2 手动触发缓存预热

把需要缓存的方法挂载到某个页面或后端接口上,手动触发缓存预热;

4.1.3 定时任务触发预热

设置定时任务,定时自动进行缓存预热。

五、缓存更新

缓存更新除了缓存服务器自带的缓存失效策略之外(Redis默认的有 6 中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:

(1)定时去清理过期的缓存;

(2)当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。

六、缓存降级

缓存降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案:

一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;

警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;

错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;

严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。

解决方案:服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。

博文参考
关注
打赏
1657692713
查看更多评论
立即登录/注册

微信扫码登录

0.0453s