Redis 缓存更新策略:原理、实现与生产实践
缓存更新是 Redis 作为缓存中间件的核心应用环节,其策略选择直接影响系统性能、数据一致性和服务稳定性。在实际业务中,缓存与数据库的双存储架构下,如何合理更新缓存、避免脏数据、减少缓存穿透 / 击穿 / 雪崩等问题,是后端开发的重点和难点。本文将从缓存更新的核心原则出发,详细解析业界主流的 6 种缓存更新策略,包括原理、实现方式、优缺点、适用场景,并结合生产环境给出选型建议和避坑指南,同时梳理缓存更新与数据一致性的核心解决方案。
一、缓存更新的核心前提与原则
在分析具体策略前,需明确缓存更新的底层逻辑和设计原则,这是所有策略选型的基础。
1. 核心前提:缓存与数据库的双存储模型
日常业务中,Redis 缓存通常作为数据库(MySQL/PostgreSQL 等)的读缓存存在,数据流转的核心逻辑为:
- 读请求:先查缓存,缓存命中则直接返回;缓存未命中则查数据库,再将数据写入缓存后返回。
- 写请求:需同时处理数据库和缓存,更新策略的本质就是定义写请求中数据库与缓存的操作顺序和方式。
2. 三大核心设计原则
3. 两个关键概念
- 强一致性:缓存数据与数据库数据实时保持一致,任意时刻读请求获取的都是最新数据,适用于核心交易场景。
- 最终一致性:缓存数据与数据库数据在短时间内可能不一致,但经过一定时间后会自动同步为最新数据,适用于非核心读多写少场景,兼顾性能和一致性。
二、业界主流 6 种缓存更新策略全解析
业界主流的缓存更新策略可分为主动更新和被动更新两大类,其中主动更新包含更新数据库后更新缓存、删除数据库后删除缓存、更新数据库后淘汰缓存等,被动更新主要为缓存过期自动淘汰,同时衍生出读写穿透和写回(缓存落地) 两种进阶策略。以下按使用频率和生产实用性排序,逐一解析各策略的核心细节。
策略 1:缓存过期自动淘汰(被动更新)
核心思想:不主动执行缓存的更新 / 删除操作,仅为缓存键设置合理的过期时间,当缓存过期后,由读请求触发缓存回源(查数据库 + 写缓存),实现缓存的被动更新。这是最简单、使用最广泛的缓存更新策略,也是大部分非核心业务的首选。
核心实现
优缺点分析
✅ 优点
❌ 缺点
适用场景
绝大多数非核心业务的读多写少场景,如:资讯详情、商品列表、用户信息(非实时)、文章评论数(非精准)等,是生产环境中性价比最高的选型,推荐优先使用。
生产优化
策略 2:更新数据库后淘汰缓存(Cache Aside)
核心思想:写请求时,先更新数据库,再删除对应的缓存键;读请求遵循「缓存优先,未命中则回源写缓存」,是兼顾一致性和性能的经典主动更新策略,也被称为「旁路缓存策略」,是核心业务读多写少场景的主流选择。
核心实现
关键问题:为何是「删缓存」而非「更缓存」?
这是缓存更新的核心避坑点,若将策略改为「更新数据库后更新缓存」,在并发写请求下会产生脏数据,举例说明:
-
场景:两个并发写请求 A 和 B,操作同一数据 ID。
- 写请求 A:更新数据库为数据 A;
- 写请求 B:更新数据库为数据 B(因网络 / 线程调度,B 的数据库更新比 A 快);
- 写请求 B:更新缓存为数据 B;
- 写请求 A:更新缓存为数据 A;
-
结果:数据库最终是数据 B,缓存是数据 A,出现脏数据,且该脏数据会一直存在直到缓存过期,无法自动恢复。
而删除缓存则不会出现此问题:无论并发写的顺序如何,最终都是删除缓存,后续读请求会回源数据库获取最新数据,写入缓存后保证数据一致。
优缺点分析
✅ 优点
❌ 缺点
适用场景
核心业务的读多写少场景,如:商品详情(库存 / 价格)、订单基础信息、用户余额(非实时交易)等,要求数据一致性高、可容忍毫秒级的短暂不一致,是生产环境中核心业务的首选策略。
生产优化
解决缓存删除失败:
- 方式 1:异步重试:通过消息队列(RocketMQ/Kafka)或本地消息表,将缓存删除操作异步化,若删除失败则定时重试,保证最终删除成功。
- 方式 2:缓存设置过期时间:为缓存键增加兜底过期时间,即使删除失败,缓存也会自然过期,避免脏数据永久存在。
解决极短时间的脏数据:若业务要求极致一致性,可在写请求时增加分布式锁,保证同一数据的写请求串行执行,缩小不一致窗口。
策略 3:先删除缓存,再更新数据库
核心思想:写请求时,先删除缓存键,再更新数据库;读请求仍遵循「缓存优先,未命中则回源写缓存」,是一种尝试追求「强一致性」的主动更新策略,但存在严重的并发脏数据问题,生产环境中不推荐直接使用。
核心实现
核心问题:并发读 + 写导致的脏数据
这是该策略的致命缺陷,在并发读和写请求下,会产生长期脏数据,举例说明:
-
场景:写请求 A 更新数据 ID,读请求 B 查询同一数据 ID,二者并发执行。
- 写请求 A:删除缓存键;
- 读请求 B:缓存未命中,查数据库(此时数据库尚未被 A 更新,获取到旧数据);
- 写请求 A:更新数据库为新数据;
- 读请求 B:将旧数据写入缓存;
-
结果:数据库是新数据,缓存是旧数据,形成脏数据,且该脏数据会一直存在直到缓存过期或被再次删除,不一致窗口可能很长。
优缺点分析
✅ 优点:理论上若无并发请求,能保证缓存与数据库的强一致性,读请求不会获取到旧数据。
❌ 缺点
适用场景
无并发读的纯写场景(几乎不存在),或低并发、对一致性要求极高且能接受数据库压力的小众场景,生产环境中严禁直接使用。
补救方案(仅作了解,不推荐落地)
可通过分布式锁将同一数据的读请求和写请求串行化,即写请求加锁后,读请求必须等待写请求完成才能执行,彻底避免并发问题。但此方案会导致读请求阻塞,大幅降低系统并发性能,违背缓存的设计初衷,性价比极低。
策略 4:更新数据库后更新缓存(不推荐)
核心思想:写请求时,先更新数据库,再更新缓存;读请求遵循「缓存优先」,是最直观的缓存更新策略,但存在严重的并发缓存覆盖问题,生产环境中几乎不使用。
核心实现
核心问题:并发写的缓存覆盖(前文已提及,此处再强化)
多个并发写请求会因执行顺序问题,导致数据库是最新数据,缓存是旧数据,且脏数据会长期存在,举例见「策略 2」的关键问题部分,此问题无低成本的有效解决方案。
优缺点分析
✅ 优点:实现最直观,开发门槛最低,读请求缓存命中率极高,几乎无回源开销。
❌ 缺点
适用场景
无并发写的单线程场景(如后台管理系统的低频写操作),生产环境的高并发业务严禁使用。
策略 5:读写穿透(Read/Write Through)
核心思想:将缓存作为数据访问的唯一入口,业务层不直接操作数据库,所有读 / 写请求都通过缓存层转发,由缓存层负责与数据库的交互和数据更新,属于缓存驱动数据的高级策略,也被称为「缓存作为主存储」。
核心实现
该策略将缓存层分为缓存命中和缓存未命中两种情况处理,全程由缓存层主导:
关键特性
- 业务层完全屏蔽数据库,仅与缓存交互,降低业务层的开发复杂度。
- 缓存层与数据库的交互是同步的,保证数据的强一致性。
- 缓存中的数据都是有效数据,无过期键(可按需设置兜底过期),避免缓存击穿 / 穿透。
优缺点分析
✅ 优点
❌ 缺点
适用场景
中大型分布式系统的核心业务,如:金融交易、电商支付、核心库存管理等,要求强一致性且希望简化业务层逻辑,适合有成熟架构团队的场景。
策略 6:写回(Write Back,也叫缓存落地)
核心思想:与读写穿透类似,将缓存作为数据访问的唯一入口,但写请求时,缓存层先更新自身数据,异步更新数据库,数据库仅作为数据持久化的兜底存储,属于高性能优先的高级缓存策略,也被称为「延迟持久化」。
核心实现
全程由缓存层主导,核心差异在写请求的异步化:
关键特性
- 写请求是异步非阻塞的,响应速度极快,是所有策略中性能最高的。
- 缓存层是主存储,数据库是从存储,数据最终会通过异步操作落地到数据库,保证数据持久化。
- 可设置批量更新策略,将多个小的写请求合并为一个数据库批量操作,降低数据库的 IO 压力。
优缺点分析
✅ 优点
❌ 缺点
适用场景
超高并发写的非核心业务,如:短视频点赞、评论数、文章阅读量、用户行为统计等,能容忍短暂的数据丢失,优先保证高并发和高性能,适合大型互联网公司的高流量场景。
三、缓存更新策略的生产选型指南
以上 6 种策略各有优劣,无绝对的 “最优策略”,选型的核心是结合业务场景的「数据一致性要求」「读写并发量」「开发 / 架构成本」 进行综合判断。以下是生产环境的通用选型优先级和场景化选型建议,覆盖 99% 的后端业务场景。
1. 通用选型优先级(从高到低)
缓存过期自动淘汰 → 更新数据库后淘汰缓存 → 读写穿透 → 写回 → 先删缓存再更数据库 / 更新数据库后更缓存(后两者几乎不选)
- 小公司 / 快速迭代业务:优先选缓存过期自动淘汰,开发快、问题少、性价比高。
- 中大型公司 / 核心业务:优先选更新数据库后淘汰缓存,兼顾一致性和性能,易落地。
- 大型分布式系统 / 极致一致性要求:选读写穿透,需配套成熟的架构设计。
- 超高并发写 / 非核心统计业务:选写回,需做好数据丢失的兜底方案。
2. 场景化精准选型
| 资讯 / 文章 / 商品列表 | 最终一致性(容忍分钟级不一致) | 读多写极少 | 缓存过期自动淘汰 | 实现简单,性能最优,无需关注缓存更新 |
| 商品详情 / 订单基础信息 / 用户余额 | 最终一致性(容忍毫秒级不一致) | 读多写少 | 更新数据库后淘汰缓存 | 一致性好,规避并发脏数据,易落地 |
| 金融交易 / 电商支付 / 核心库存 | 强一致性 | 读写均有,并发中等 | 更新数据库后淘汰缓存 + 分布式锁 / 读写穿透 | 保证数据实时一致,核心数据零误差 |
| 短视频点赞 / 评论数 / 行为统计 | 最终一致性(容忍秒级不一致) | 超高并发写,读多 | 写回 + 缓存过期兜底 | 异步写性能极致,适配高并发,统计数据可容忍短暂不一致 |
| 后台管理系统 / 低频写操作 | 强一致性 | 低并发,读写少 | 更新数据库后更新缓存 | 无并发问题,实现直观,开发效率高 |
四、缓存更新的核心避坑与生产优化
无论选择哪种缓存更新策略,都需要关注数据一致性保障和缓存问题规避,以下是生产环境中最常遇到的坑和对应的通用优化方案,适用于所有策略。
1. 核心避坑:解决缓存删除失败导致的脏数据
该问题主要出现在「更新数据库后淘汰缓存」策略中,数据库更新成功但缓存删除失败,是生产环境中脏数据的主要来源之一,必须做兜底处理,推荐两种方案:
方案 1:本地消息表 + 定时重试(适合中小型系统)
方案 2:消息队列异步重试(适合中大型分布式系统)
通用兜底:无论采用哪种重试方案,都要为缓存键设置过期时间,即使重试失败,缓存也会自然过期,避免脏数据永久存在。
2. 通用优化:规避缓存三大问题(穿透 / 击穿 / 雪崩)
缓存更新策略的执行过程中,极易引发缓存穿透、击穿、雪崩,需结合策略做针对性优化,这是缓存使用的必做功课:
缓存穿透(请求不存在的数据,穿透到数据库):
- 方案:布隆过滤器过滤不存在的键 + 缓存空值(设置短过期时间,如 5 分钟)。
缓存击穿(热点键过期 / 被删除,大量读请求穿透到数据库):
- 方案:热点键永不过期 + 分布式锁(互斥锁)实现缓存回源的单线程化。
缓存雪崩(大量缓存同时过期 / 被删除,数据库被压垮):
- 方案:为缓存键增加随机过期时间 + 缓存集群高可用(主从 + 哨兵 / 集群) + 数据库做限流 / 降级 / 熔断。
3. 性能优化:减少 Redis 的无效操作
4. 一致性优化:缩小数据不一致窗口
对于「更新数据库后淘汰缓存」策略,其不一致窗口存在于「数据库更新完成」到「缓存删除完成」的极短时间,可通过以下方式进一步缩小:
五、缓存更新与数据一致性的核心总结
缓存更新的本质,是在「性能」和「数据一致性」之间做权衡,业界没有 “鱼和熊掌兼得” 的完美策略,所有选择都是基于业务场景的取舍:
六、全文总结
Redis 缓存更新策略是后端开发的核心知识点,其选型直接决定系统的性能和稳定性。本文详细解析的 6 种策略中,缓存过期自动淘汰和更新数据库后淘汰缓存是生产环境的主流选择,覆盖了绝大多数业务场景;读写穿透和写回属于高级策略,适合有成熟架构团队的中大型分布式系统;而先删缓存再更数据库、更新数据库后更缓存因存在严重的并发脏数据问题,几乎不推荐使用。
在实际开发中,无需拘泥于固定策略,应根据业务的「一致性要求」「读写并发量」和「开发成本」做灵活组合,例如:核心热点数据采用「更新数据库后淘汰缓存 + 永不过期」,非核心数据采用「缓存过期自动淘汰」,统计数据采用「写回」。同时,必须做好异常容错和缓存问题规避,通过重试机制、过期兜底、分布式锁等方案,让缓存更新策略更健壮、更适配生产环境的复杂场景。
缓存的核心价值是提升系统性能,而合理的缓存更新策略,是让这份价值发挥到极致的关键。
