Redis之缓存更新策略

发布于:2025-04-14 ⋅ 阅读:(30) ⋅ 点赞:(0)

缓存更新策略

在这里插入图片描述

一、策略对比

内存淘汰 超时剔除 主动更新
说明 不用自己维护,
利用Redis的内存淘汰机制,
当内存不足时自动淘汰部分数据。下次查询时更新缓存。
给缓存数据添加TTL时间,
到期后自动删除缓存。
下次查询时更新缓存。
编写业务逻辑,
在修改数据库的同时,更新缓存
一致性 一般
维护成本

业务场景

  • 低一致性需求:使用内存淘汰机制。例如商铺类型的查询缓存
  • 高一致性需求:主动更新,并以超时剔除作为兜底方案。例如店铺详情查询的缓存

二、常见的缓存更新策略

  1. Cache Aside Pattern旁路缓存模式):由缓存的调用者,在更新数据库的同时更新缓存。
    • 原理
      • 读操作:先查询缓存,若命中则直接返回;未命中则从数据库加载数据并写入缓存。
      • 写操作:先更新数据库,再删除缓存(或更新缓存)。
    • 优点:简单易实现,适用于大多数场景。
    • 缺点:可能存在短暂的数据不一致(如并发写入导致脏数据)。
    • 场景:读多写少、对一致性要求不高的场景。
  2. Read / Write Through Pattern读穿/写穿):缓存与数据库整合为一个服务,由服务来维护一致性。调用者调用该服务,无需关心缓存一致性问题。
    • 读穿透(Read-Through)
      • 当缓存未命中时,由缓存服务自动从数据库加载数据并回填缓存 。
    • 写穿透(Write-Through)
      • 数据更新时,同时写入缓存和数据库,保证两者同步。
    • 优点:对业务透明,数据一致性高。
    • 缺点:写操作延迟较高,依赖缓存服务的可靠性。
    • 场景:强一致性要求的场景。
  3. Write Behind Caching Pattern异步写回):调用者只操作缓存,由其他线程异步地将缓存数据持久化到数据库,保证最终一致性。
    • 原理
      • 写操作仅更新缓存,异步批量将数据持久化到数据库。
    • 优点:写性能极高,减少数据库压力。
    • 缺点:存在数据丢失风险(如系统崩溃时为同步完成的数据)。
    • 场景:允许短暂不一致、写密集型场景(如日志系统)。
  4. 被动更新(TTL刷新)
    • 原理
      • 为缓存设置过期时间(Time-To-Live,TTL),到期后自动失效并从数据库重新加载。
    • 优点:实现简单,避免长期脏数据。
    • 缺点:无法主动控制更新实际,可能出现缓存雪崩(大量缓存同时失效)。
    • 场景:数据变动频率较低的场景。
  5. 主动更新(事件驱动)
    • 原理
      • 当数据库数据变更时(如通过消息队列或出发期),主动通知应用层更新或删除对应缓存。
    • 优点:实时性高,减少无效查询。
    • 缺点:依赖外部系统协同,复杂度较高。
    • 场景:分布式系统中强一致性要求的场景(如电商库存更新)。

注意在使用Cache Aside Pattern方式的工程中,操作缓存和数据库时有三个问题需要考虑

  1. 删除缓存还是更新缓存?
    • 更新缓存:每次更新数据库都更新缓存,无效写操作较多
    • 删除缓存【推荐】:更新数据库时让缓存失效,查询时再更新缓存
  2. 如何保证缓存与数据库的操作同时成功或同时失败?
    • 单体系统:将缓存与数据库操作放在一个事务
    • 分布式系统,利用TCC等分布式事务方案
  3. 先操作缓存还是先操作数据库
    • 先删除缓存,再操作数据库
    • 先操作数据库,再删除缓存 【推荐】

三、如何选择策略

  • 数据一致性要求:强一致性选 Write-Through 或主动更新;弱一致性可选 Cache-Aside 或 TTL。
  • 读写比例:读多写少优先 Cache-Aside;写多考虑 Write-Behind。
  • 系统复杂度:简单场景用 TTL 或 Cache-Aside;复杂场景用事件驱动。
  • 性能需求:高频写入可用 Write-Behind 降低延迟。

四、实际应用示例

  • 电商商品详情页:使用 Cache-Aside + TTL,配合消息队列主动更新热点商品。
  • 社交点赞数统计:采用 Write-Behind 异步更新数据库,保证快速响应。
  • 全局配置信息:Read-Through 确保每次读取都是最新配置。

五、使用 Cache-Aside + TTL 的方式,实现缓存商铺信息详情

1.引入StringRedisTemplate

    @Resource
    private StringRedisTemplate stringRedisTemplate;

2.将查询商铺信息加入缓存

命中
未命中
存在
不存在
开始
提交商铺id
从Redis查询商铺缓存
判断缓存是否命中
返回商铺信息
结束
根据id查询数据库
判断商铺是否存在
将商铺数据写入Redis
返回404
    @Override
    public Result queryById(Long id) {
        // 1.从redis查询商铺缓存
        String key = CACHE_SHOP_KEY + id;
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        // 2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)) {
            // 3.存在,直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        }
        // 4.不存在,根据id查询数据库
        Shop shop = getById(id);
        if (shop == null) {
            // 5.数据库不存在,返回错误
            return Result.fail("店铺不存在!");
        }
        // 6.存在,写入redis,加入过期时间
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
        return Result.ok(shop);
    }

3.更新商铺信息时移除缓存

    @Override
    public Result update(Shop shop) {
        if (shop.getId() == null) {
            return Result.fail("店铺id不能为空!");
        }
        // 1.更新数据库
        updateById(shop);
        // 2.删除缓存
        stringRedisTemplate.delete(CACHE_SHOP_KEY + shop.getId());
        return Result.ok();
    }

总结

使用 Cache-Aside + TTL 缓存更新策略的最佳实践方案:

  1. 低一致性需求:使用Redis自带的内存淘汰机制
  2. 高一致性需求:主动更新,并以超时剔除作为兜底方案
    • 读操作:
      • 缓存命中则直接返回
      • 缓存未命中则查询数据库,并写入缓存,设定超时时间
    • 写操作:
      • 先写数据库,然后再删除缓存
      • 要确保数据库与缓存操作的原子性

六、注意事项

  • 缓存穿透:恶意请求不存在的 key,可通过布隆过滤器防御。
  • 缓存雪崩:大量 Key 同时失效,需设置随机 TTL 或禁用失效时间。
  • 缓存击穿:热点 Key 失效瞬间高并发,可用互斥锁(Mutex Luck)保护。

缓存更新策略的选择需结合业务场景、数据特点和技术架构综合权衡,没有绝对的最优解,只有最适合的方案。


网站公告

今日签到

点亮在社区的每一天
去签到