Redis 在缓存架构中的角色

Redis 在缓存架构中并非简单的键值对存储,而是承担着加速层、缓冲层与状态协调层三重角色。其核心价值在于将热点数据从慢速的磁盘型数据库(如 MySQL、PostgreSQL)提升至内存层级,同时通过原子操作与数据结构能力,在缓存与数据库之间建立可预测的一致性边界。

Redis 作为缓存层的核心优势

优势维度 具体表现 技术支撑
极致延迟 平均读写延迟 < 1ms(P99 < 5ms) 全内存数据存储、单线程事件循环、非阻塞 I/O
高吞吐量 单实例可达 10万+ QPS(读场景更高) 纯内存操作 + 多路复用 I/O 模型
丰富数据结构 String、Hash、List、Set、Sorted Set、HyperLogLog、Bitmap、Stream 直接支持缓存对象、计数器、排行榜、布隆过滤器等场景
原子性保证 单命令原子 + Lua 脚本原子 + 事务乐观锁 单线程执行模型保证命令级原子性
淘汰与过期 8 种淘汰策略(LRU、LFU、TTL、随机等)+ 惰性删除 + 定期删除 精确控制缓存容量与数据新鲜度
持久化兜底 RDB 快照 + AOF 日志,重启后快速恢复缓存数据 降低缓存雪崩时数据库回源压力
关键认知:Redis 的缓存优势不仅在于"快",更在于"可控"。通过 TTL、淘汰策略、原子操作和 Lua 脚本,开发者可以在缓存层实现业务逻辑的精确编排,而非仅仅充当被动存储。

Redis 与数据库的典型交互模式

缓存与数据库的交互模式决定了数据一致性的最终形态。以下是生产环境中四种最常用的模式,每种模式对应不同的一致性等级与适用场景。

模式一:Cache-Aside(旁路缓存)

这是最广泛使用的模式,应用层同时感知缓存与数据库的存在,并主动管理数据流向。

  1. 读流程:应用先查询 Redis → 命中则直接返回 → 未命中则查询数据库 → 将结果写入 Redis 并设置 TTL → 返回给调用方。
  2. 写流程:应用先更新数据库 → 然后删除 Redis 中的对应缓存(或更新缓存)。
实践建议:写流程推荐"先更新数据库,后删除缓存"(而非更新缓存)。删除操作天然幂等,且能避免并发写导致的缓存与数据库数据不一致。配合延迟双删策略可进一步降低脏读窗口。

模式二:Read-Through / Write-Through(穿透读写)

缓存层封装了与数据库的交互逻辑,应用只与缓存交互,由缓存组件(如 Redis + 代理层)负责回源与同步。

  • Read-Through:缓存未命中时,缓存层自动加载数据库数据并返回,应用无感知。
  • Write-Through:应用写入缓存时,缓存层同步将数据写入数据库,保证写入操作的强一致性。

模式三:Write-Behind(异步回写)

应用只写入缓存,缓存层异步批量将数据回写至数据库。适用于写吞吐极高、对瞬时一致性要求不高的场景(如日志、点击流)。

风险提示:Write-Behind 模式下,若 Redis 在异步回写前宕机,未持久化的数据将永久丢失。必须配合 AOF 持久化 + 至少一个从节点,并接受最终一致性模型。

模式四:Cache-Aside + 订阅变更(近实时一致性)

在 Cache-Aside 基础上,引入数据库变更捕获(如 Canal、Debezium)监听 binlog,当数据库数据变更时,通过消息队列通知缓存层删除或更新对应缓存。

模式 一致性等级 写吞吐 实现复杂度 典型场景
Cache-Aside 最终一致性(秒级窗口) 中 低 通用业务缓存
Read/Write-Through 强一致性(同步写库) 低 中 配置、元数据
Write-Behind 最终一致性(分钟级) 极高 高 日志、埋点、计数
Cache-Aside + binlog 近实时一致性(毫秒级) 中 高 订单、库存、价格
Redis 与数据库典型交互模式对比 应用层 Cache-Aside 读:缓存未命中→查库→回写 写:更新库→删除缓存 Read/Write-Through 缓存层代理数据库读写 应用只与缓存交互 Write-Behind 异步批量回写数据库 高吞吐、最终一致性 Cache-Aside + binlog 订阅(近实时一致性) 数据库 选择依据:一致性要求越高,写吞吐越低,实现复杂度越高。生产环境通常混合使用多种模式。

Redis 的原子性保证

Redis 的原子性是其作为缓存一致性基石的关键能力。理解原子性的三个层次,有助于在缓存与数据库交互时设计正确的并发控制策略。

层次一:单命令原子性

由于 Redis 采用单线程事件循环模型,每个命令在执行期间不会被其他命令中断。这意味着 INCR、DECR、GETSET、HSET 等操作天然具备原子性。

# 原子递增计数器,无需加锁
INCR article:read_count:1001

# 原子设置并返回旧值,用于分布式锁续期
GETSET lock:order:12345 "new_owner"

层次二:事务原子性(MULTI/EXEC + WATCH)

Redis 事务通过 MULTI 开启,EXEC 执行。事务中的所有命令会被序列化并按顺序执行,期间不会插入其他客户端的命令。但 Redis 事务不支持回滚——如果事务中某条命令执行失败,后续命令仍会继续执行。

关键区别:Redis 事务保证的是"隔离性"(Isolation)和"原子性"(Atomicity,即全部执行或全部不执行,但失败不回滚),而非传统数据库的 ACID 原子性。使用 WATCH 命令可实现乐观锁,在事务执行前监控 key 是否被其他客户端修改。
# 使用 WATCH 实现乐观锁扣减库存
WATCH product:stock:2001
stock = GET product:stock:2001
if stock > 0
    MULTI
    DECR product:stock:2001
    EXEC
else
    UNWATCH
    return "库存不足"

层次三:Lua 脚本原子性

Lua 脚本是 Redis 实现复杂原子操作的首选方案。整个脚本作为一个整体被 Redis 单线程执行,脚本执行期间不会处理任何其他命令。这使得开发者可以在缓存层实现包含条件判断、循环、多 key 操作的原子逻辑。

-- Lua 脚本:原子扣减库存并记录操作日志
local stock_key = KEYS[1]
local log_key = KEYS[2]
local quantity = tonumber(ARGV[1])
local current = redis.call('GET', stock_key)

if not current or tonumber(current) < quantity then
    return 0  -- 库存不足
end

redis.call('DECRBY', stock_key, quantity)
redis.call('LPUSH', log_key, 'deduct:' .. quantity .. ' at ' .. ARGV[2])
return 1  -- 扣减成功
原子性层次 粒度 是否可回滚 适用场景
单命令 单个 key 操作 不适用 计数器、简单状态切换
事务 (MULTI/EXEC) 多命令批量 否(失败继续执行) 批量更新、乐观锁扣减
Lua 脚本 多 key 复杂逻辑 否(但可脚本内判断) 库存扣减、限流、分布式锁续期
Redis 原子性保证的三层模型 第一层:单命令原子性 单线程模型保证 INCR / DECR / GETSET 等命令执行期间不被中断 第二层:事务原子性(MULTI / EXEC + WATCH) 批量命令顺序执行,WATCH 实现乐观锁,失败不回滚但可重试 第三层:Lua 脚本原子性 整个脚本作为原子单元执行,支持条件判断、循环、多 key 复杂逻辑 选择原则:能用单命令不用事务,能用事务不用 Lua,Lua 脚本是复杂原子操作的最终方案
架构建议:在缓存一致性场景中,优先使用 Lua 脚本将"读缓存→判断条件→写缓存→写数据库"等复合操作封装为原子单元。这能最大程度减少并发冲突窗口,且脚本可被 Redis 缓存编译结果,性能损失极小。