缓存的设计

1. 缓存的更新机制

1.1 被动更新

为缓存设定过期时间,失效从数据库读取,再次写入缓存

调用方 暂存方(缓存) 数据提供方

被动:有效期到后,再次写入。

  1. 客户端 查数据,缓存中没有,从提供方获取,写入缓存(有一个过期时间t)。

  2. 在t内,所有的查询,都由缓存提供。所有的写,直接写数据库。

  3. 当 缓存数据 t 到点了,缓存 数据 变没有。后面的查询,回到了第1步。

适合:对数据准确性和实时性要求不高的场景。比如:商品 关注的人数。

1.2 主动更新

1.2.1 Cache Aside Pattern (更新数据库,再删除缓存)

这是最常用的更新机制

  • 失效: 应用程序从cache中获取数据,没获取到,则从数据库中读取数据,成功后,放到缓存中
  • 命中: 应用程序从缓存中获取数据,得到后直接返回
  • 更新: 先把数据库中的数据更新,成功后,在将缓存删除

在这里插入图片描述

有可能产生的问题

比方一个读操作,一个写操作的并发,读操作没有了删除缓存的操作,直接命中拿的是缓存中的数据,写操作更新了数据库中的数据,并删除了缓存,读操作读的就是老的数据

但是这种情况只是理论上存在,实际上很少出现,因为这种情况的产生是需要读操作慢于写操作,一般情况下,写操作都是比读操作慢,并且要加事务锁表,所以很少出现这种情况

1.2.2 更新数据库,更新缓存

一般也不采用。
请求被阻塞,
业务要求:修改了数据库,缓存的值需要通过大量时间的计算才能进行更新,影响了响应时间,直接删了缓存,比较节省计算时间,当用户再次去查询的时候,发现缓存的值不存在,用户只要经过一次复杂的计算就能对缓存的值进行更新

1.2.3 先删除缓存,在更新数据库

一般不采用,因为大概率 读比写快。

一个读请求和一个写请求的并发,当写请求进来,把缓存删了,更新数据库这步操作还没完成,读请求进来,发现没缓存,往数据库中读取,这个时候数据库的数据还是老的数据,读请求把老的数据写入了缓存,然后写请求才把数据更新到数据库,这就造成了数据库和缓存的双写一致性问题
在这里插入图片描述

解决双写一致性问题:延迟双删

延迟双删

就是在更新晚数据库之后,sleep一段时间,再次进行删除缓存的操作,能极大的保证双写一致性

在这里插入图片描述

昨天被高德一个面试问:说,你这个延时双删有这么几步操作。如果其中某一步失败了这么办?
删除缓存
更新数据库:事务,回滚就OK。
第二次删除缓存
重试删除:当你前面的操作,无法回滚时,为了保证后续数据的一致性,
(最便宜的做法)硬着头皮往前走,重试。
借用中间件:消息队列,重发消息。
系统外订阅:canal。binlog。
二次删除key,和我们的业务代码解耦。

1.3 Read/Write Through Pattern

这个是直接更缓存打交道,所有的增删改查都在缓存上进行,不会出现数据一致性的问题,但是缓存挂了的话,数据容易丢失

在这里插入图片描述

1.4 Write Behind Caching Pattern

更新数据的操作直接在缓存中进行,然后再异步对数据库进行更新,带来的好处就是数据的IO操作非常的快。带来的问题就是可能产生数据的丢失

2. 缓存的清理机制

2.1 时效性清理

就是给缓存设置一个过期时间,到期自动清理

2.2 数目阀值清理机制

判断缓存中的缓存的数量 达到一定值 ,对缓存进行清理。
阈值:根据自己的业务来定。1g,1m,1024个, 800 80%。

2.3 软应用清理

当空间不足的时候,会被回收

2.4 redis 的8中内存淘汰策略

a) 针对设置了过期时间的key做处理:

  1. volatile-ttl:在筛选时,会针对设置了过期时间的键值对,根据过期时间的先后进行删除,越早过期的越先被删除。

  2. volatile-random:就像它的名称一样,在设置了过期时间的键值对中,进行随机删除。

  3. volatile-lru:会使用 LRU 算法筛选设置了过期时间的键值对删除。

  4. volatile-lfu:会使用 LFU 算法筛选设置了过期时间的键值对删除
    b) 针对所有的key做处理 :

  5. allkeys-random:从所有键值对中随机选择并删除数据。

  6. allkeys-lru:使用 LRU 算法在所有数据中进行筛选删除。

  7. allkeys-lfu:使用 LFU 算法在所有数据中进行筛选删除。
    c) 不处理:

  8. noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。

LRU 算法(Least Recently Used,最近最少使用)
淘汰很久没被访问过的数据,以最近一次访问时间作为参考。(淘汰这段时间中最旧的key)

LFU 算法(Least Frequently Used,最不经常使用)
淘汰最近一段时间被访问次数最少的数据,以次数作为参考。(淘汰这段时间使用次数最少的key)

2.5 缓存清理机制的总结

  1. 时效性清理+数目阀值: 防止:短期内,密集查询,导致缓存空间的急剧增大
  2. lru+软引用:保证热数据,最大限度的提高缓存命中率

3. 缓存问题

3.1 缓存穿透

缓存中没有值,数据库中也没有这个值

产生可能原因

1、自身业务代码或者数据出现问题;
2、一些恶意攻击、 爬虫等造成大量空命中。

问题解决

1. 缓存空对象
注意:对于不存在的空对象,一定要设置过期时间!

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. 布隆过滤器

布隆过滤器说某个值存在时,这个值可能不存在;当它说不存在时,那就肯定不存在。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值