缓存—雪崩、击穿、穿透、缓存一致性

缓存和数据库的一致性问题

数据同时放在缓存和数据库中,第一次读走数据库,然后将数据放到缓存,下次读走缓存更快,加快读操作。但是写操作需要同时更新缓存和数据库。

两种方案:
1、先删缓存值,再更新数据库

  • 问题:
    假设现在是线程1删除缓存之后、更新数据库之前,线程2读数据,发现缓存中没有数据,就会到数据库去读,此时数据库里还是旧值,读到旧值并更新到缓存,之后缓存里存的就是旧值,数据库里存的线程1更新的新值。
  • 解决办法:延迟双删
    线程1估算线程2读数据+写缓存的时间t,在等待t之后去缓存中删除线程2设置的旧值。

2、先更新数据库,再删缓存值

  • 问题:删除缓存之前,别的线程会读到旧数据
  • 解决办法:删除缓存之前,在缓存中暂存读操作,删除之后再执行读操作。

如何选择?
优先使用方案一
因为方案二中,如果删除缓存失败,那缓存中存的就是旧数据,之后的所有线程读到的就全部是错误的数据了。虽然可以借助消息队列的失败重试机制,但是问题会变得更加复杂,怎么保证消息不丢失,及时成功删除,消息延迟导致缓存删除的更晚,读到旧数据的线程更多。

可以不删除缓存值,而是更新缓存值吗?
尝试分析更新缓存值的两种情况,看是否可以

1、先更新数据库,再更新缓存:
如果更新数据库成功,但缓存更新失败,此时数据库中是最新值,但缓存中是旧值,后续的读请求会直接命中缓存,得到旧值

2、先更新缓存,再更新数据库:
如果更新缓存成功,但数据库更新失败,此时缓存中是最新值,数据库中是旧值,后续读请求会直接命中缓存,得到的是最新值,短期对业务影响不大。
但是,一旦缓存过期被淘汰,读请求就会从数据库中重新加载旧值到缓存中,之后的读请求会从缓存中得到旧值

所以还是删除缓存比较好。

缓存雪崩-大量数据同时失效

什么是缓存雪崩?
缓存雪崩是指大量的应用请求无法在Redis缓存中进行处理,紧接着,应用将大量请求发送到数据库层,导致数据库层的压力激增。造成数据库也会挂掉的情况。

解决方案:

  • 数据预热
    数据加热的含义就是在正式部署之前,先把可能大量访问的数据先预先访问一遍,这样这些可能大量访问的数据就会被加载到缓存中。

  • 加随机值使得缓存失效时间分散开
    不同缓存的失效时间不能一致,同一种缓存的失效时间也尽量随机(最小值–>最大值),比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

  • 服务降级
    保证核心服务的稳定,暂停掉对非核心服务的支持或有限支持。

缓存击穿-热点key失效

什么是缓存击穿?
热点key失效了,在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃。和缓存雪崩相比,缓存击穿失效的数据量要小很多,应对方法也不一样。

解决办法
对于访问特别频繁的热点数据,就不设置过期时间了

缓存穿透-访问不存在的数据

缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不命中

解决办法:

1、缓存空对象
对于不存在的数据,同样设置一个特殊的value到缓存中,比如当数据库中查出的用户信息为空的时候,设计某个值这样具有特殊含义的字符串到缓存中。这样下次请求 缓存的时候还是可以命中缓存,即直接从缓存返回结果,不查询数据库
但是,这种方式可能会把大量无效的数据加入到缓存中,如果担心大量无效数据占满缓存的话还可以考虑方案二,即使用布隆过滤器做前置过滤

String get(String key) {
    // 从缓存中获取数据
    String cacheValue = cache.get(key);
    // 缓存为空
    if (StringUtils.isBlank(cacheValue)) {
        // 从存储中获取
        String storageValue = storage.get(key);
        cache.set(key, storageValue);
        // 如果存储数据为空, 需要设置一个过期时间(300秒)
        if (storageValue == null) {
            cache.expire(key, 60 * 5);
        }
        return storageValue;
    } else {
        // 缓存非空
        return cacheValue;
    }
}

2、 布隆过滤器拦截
对于恶意攻击,向服务器请求大量不存在的数据造成的缓存穿透,还可以用布隆过滤器先做一次过滤,对于不存在的数据布隆过滤器一般都能够过滤掉,不让请求再往后端发送。
当布隆过滤器说某个值存在时,这个值可能不存在;当它说不存在时,那就肯定不存在。

布隆过滤器原理:
向布隆过滤器中添加 key 时,会使用多个 hash 函数对 key 进行 hash 算得一个整数索引值然后对位数组长度进行取模运算得到一个位置,每个 hash 函数都会算得一个不同的位置。再把位数组的这几个位置都置为 1 就完成了 add 操作。

向布隆过滤器询问 key 是否存在时,跟 add 一样,也会把 hash 的几个位置都算出来,看看位数组中这几个位置是否都为 1,只要有一个位为 0,那么说明布隆过滤器中这个key 不存在。如果都是 1,这并不能说明这个 key 就一定存在,只是极有可能存在,因为这些位被置为 1 可能是因为其它的 key 存在所致。如果这个位数组比较稀疏,这个概率就会很大,如果这个位数组比较拥挤,这个概率就会降低。

这种方法适用于数据命中不高、 数据相对固定的应用场景。

其实,方案二可以和方案一同时使用,即将布隆过滤器前置,对于误判的情况再保存特殊值到缓存,双重保险 避免无效数据查询请求打到数据库

缓存雪崩、击穿、穿透总结

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
缓存击穿穿透雪崩是分布式系统中常见的三种缓存失效问题,它们各自描述了不同的缓存失效场景: 1. **缓存击穿(Cache Miss due to Write)**: 当一个热点数据(即频繁访问的数据)在短时间内被大量写入,而缓存中的旧值还没有过期,新写入的数据会直接覆盖缓存。此时,如果有大量的请求到来,都会触发缓存查询数据库,导致缓存未命中(Cache Miss),性能下降。 2. **缓存穿透(Cache Miss by Intentional Key)**: 这种情况是恶意用户故意发送一些在缓存中不存在(key从未被设置过缓存值)的键值对,迫使缓存系统频繁地去数据库查找对应的数据,从而浪费了大量的数据库查询次数,影响缓存效率。 3. **缓存雪崩(Cache Disaster)**: 当缓存集群中的大部分或所有缓存都同时失效(比如因为缓存过期策略导致),且这些缓存都关联着同一个热点数据,当大量请求同时到达时,数据库会接收到大量的并发请求,造成数据库压力过大,服务响应速度急剧下降。 为了应对这些问题,通常采用的策略有: - 设置合理的缓存失效策略,比如使用TTL(Time To Live)和最近最少使用(Least Recently Used, LRU)算法。 - 对于热点数据,可以考虑设置缓存预热或者缓存复制。 - 对于恶意的缓存穿透攻击,可以使用限流、IP黑名单等方法防护。 - 集群化缓存,利用一致性哈希或分区策略来分散失效带来的冲击。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值