Redis之缓存更新策略
缓存更新策略
文章目录
- 缓存更新策略
- 一、策略对比
- 二、常见的缓存更新策略
- 三、如何选择策略
- 四、实际应用示例
- 五、使用 Cache-Aside + TTL 的方式,实现缓存商铺信息详情
- 1.引入StringRedisTemplate
- 2.将查询商铺信息加入缓存
- 3.更新商铺信息时移除缓存
- 总结
- 六、注意事项
一、策略对比
内存淘汰 | 超时剔除 | 主动更新 | |
---|---|---|---|
说明 | 不用自己维护, 利用Redis的内存淘汰机制, 当内存不足时自动淘汰部分数据。下次查询时更新缓存。 | 给缓存数据添加TTL时间, 到期后自动删除缓存。 下次查询时更新缓存。 | 编写业务逻辑, 在修改数据库的同时,更新缓存 |
一致性 | 差 | 一般 | 好 |
维护成本 | 无 | 低 | 高 |
业务场景:
- 低一致性需求:使用内存淘汰机制。例如商铺类型的查询缓存
- 高一致性需求:主动更新,并以超时剔除作为兜底方案。例如店铺详情查询的缓存
二、常见的缓存更新策略
Cache Aside Pattern
(旁路缓存模式):由缓存的调用者,在更新数据库的同时更新缓存。- 原理:
- 读操作:先查询缓存,若命中则直接返回;未命中则从数据库加载数据并写入缓存。
- 写操作:先更新数据库,再删除缓存(或更新缓存)。
- 优点:简单易实现,适用于大多数场景。
- 缺点:可能存在短暂的数据不一致(如并发写入导致脏数据)。
- 场景:读多写少、对一致性要求不高的场景。
- 原理:
Read / Write Through Pattern
(读穿/写穿):缓存与数据库整合为一个服务,由服务来维护一致性。调用者调用该服务,无需关心缓存一致性问题。- 读穿透(Read-Through):
- 当缓存未命中时,由缓存服务自动从数据库加载数据并回填缓存 。
- 写穿透(Write-Through):
- 数据更新时,同时写入缓存和数据库,保证两者同步。
- 优点:对业务透明,数据一致性高。
- 缺点:写操作延迟较高,依赖缓存服务的可靠性。
- 场景:强一致性要求的场景。
- 读穿透(Read-Through):
Write Behind Caching Pattern
(异步写回):调用者只操作缓存,由其他线程异步地将缓存数据持久化到数据库,保证最终一致性。- 原理:
- 写操作仅更新缓存,异步批量将数据持久化到数据库。
- 优点:写性能极高,减少数据库压力。
- 缺点:存在数据丢失风险(如系统崩溃时为同步完成的数据)。
- 场景:允许短暂不一致、写密集型场景(如日志系统)。
- 原理:
- 被动更新(TTL刷新)
- 原理:
- 为缓存设置过期时间(Time-To-Live,TTL),到期后自动失效并从数据库重新加载。
- 优点:实现简单,避免长期脏数据。
- 缺点:无法主动控制更新实际,可能出现缓存雪崩(大量缓存同时失效)。
- 场景:数据变动频率较低的场景。
- 原理:
- 主动更新(事件驱动)
- 原理:
- 当数据库数据变更时(如通过消息队列或出发期),主动通知应用层更新或删除对应缓存。
- 优点:实时性高,减少无效查询。
- 缺点:依赖外部系统协同,复杂度较高。
- 场景:分布式系统中强一致性要求的场景(如电商库存更新)。
- 原理:
注意:在使用Cache Aside Pattern
方式的工程中,操作缓存和数据库时有三个问题需要考虑:
- 删除缓存还是更新缓存?
- 更新缓存:每次更新数据库都更新缓存,无效写操作较多
- 删除缓存【推荐】:更新数据库时让缓存失效,查询时再更新缓存
- 如何保证缓存与数据库的操作同时成功或同时失败?
- 单体系统:将缓存与数据库操作放在一个事务
- 分布式系统,利用TCC等分布式事务方案
- 先操作缓存还是先操作数据库
- 先删除缓存,再操作数据库
- 先操作数据库,再删除缓存 【推荐】
三、如何选择策略
- 数据一致性要求:强一致性选 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
@Resourceprivate StringRedisTemplate stringRedisTemplate;
2.将查询商铺信息加入缓存
@Overridepublic 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.更新商铺信息时移除缓存
@Overridepublic 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 缓存更新策略的最佳实践方案:
- 低一致性需求:使用Redis自带的内存淘汰机制
- 高一致性需求:主动更新,并以超时剔除作为兜底方案
- 读操作:
- 缓存命中则直接返回
- 缓存未命中则查询数据库,并写入缓存,设定超时时间
- 写操作:
- 先写数据库,然后再删除缓存
- 要确保数据库与缓存操作的原子性
- 读操作:
六、注意事项
- 缓存穿透:恶意请求不存在的 key,可通过布隆过滤器防御。
- 缓存雪崩:大量 Key 同时失效,需设置随机 TTL 或禁用失效时间。
- 缓存击穿:热点 Key 失效瞬间高并发,可用互斥锁(Mutex Luck)保护。
缓存更新策略的选择需结合业务场景、数据特点和技术架构综合权衡,没有绝对的最优解,只有最适合的方案。