2、Redis缓存基础:Redis数据结构回顾、缓存读写模式、TTL与过期策略

2.1 Redis核心数据结构回顾

Redis 提供了丰富的数据结构,每种结构都有其特定的应用场景和底层编码方式。在缓存一致性设计中,选择正确的数据结构直接影响性能与一致性保障的复杂度。

数据结构 底层编码 典型缓存场景 一致性关注点
String int / embstr / raw Session、计数器、简单KV 原子操作(INCR/DECR)避免竞态
Hash ziplist / hashtable 对象属性缓存(用户信息) 字段级更新,减少大对象全量覆盖
List quicklist 消息队列、最新列表 LPUSH/RPOP 与数据库顺序一致
Set intset / hashtable 标签、去重集合 SADD/SREM 与关系型数据同步
Sorted Set ziplist / skiplist 排行榜、延迟队列 Score 与业务时间戳对齐
选型建议: 缓存对象属性频繁部分更新时,优先使用 Hash 而非 String 序列化整个对象,可减少网络传输和序列化开销,同时降低并发写冲突概率。

2.2 缓存读写模式

缓存读写模式决定了应用程序如何与缓存及数据库交互。不同的模式在一致性、复杂度和吞吐量上各有取舍。

2.2.1 Cache-Aside(旁路缓存)

Cache-Aside 是最常用的模式,应用程序同时感知缓存和数据库的存在。

  • 读流程: 先查缓存,命中则直接返回;未命中则查数据库,将结果写入缓存后返回。
  • 写流程: 先更新数据库,再删除缓存(或更新缓存)。
// 读流程伪代码
public Object get(String key) {
    Object value = redis.get(key);
    if (value == null) {
        value = db.query(key);
        if (value != null) {
            redis.set(key, value, ttl);
        }
    }
    return value;
}

// 写流程伪代码(推荐:先更新DB,再删除缓存)
public void update(String key, Object newValue) {
    db.update(key, newValue);
    redis.del(key); // 删除缓存,而非更新
}
常见陷阱: 先删除缓存再更新数据库会导致并发读请求在删除后、更新前读到旧数据并回填缓存,造成长期不一致。务必采用 先更新数据库,后删除缓存 的顺序。
Cache-Aside 读写流程 应用服务 Redis 缓存 数据库 读流程: ① 查缓存 ② 未命中 ③ 查数据库 ④ 回写缓存 写流程: ① 更新数据库 ② 删除缓存 关键原则:读时回填,写时淘汰。删除缓存比更新缓存更安全,可避免并发写导致的脏数据。 TTL 作为兜底机制,确保即使删除失败,缓存也会自动过期。

2.2.2 Read-Through / Write-Through(穿透读写)

Read-Through 和 Write-Through 将缓存抽象为数据源,应用程序只与缓存层交互,由缓存层负责与数据库同步。

模式 读行为 写行为 一致性保障
Read-Through 缓存未命中时,缓存组件自动加载数据库数据并返回 通常配合 Write-Through 或 Write-Behind 缓存层控制加载逻辑,减少应用复杂度
Write-Through — 写操作先写入缓存,缓存同步写入数据库,成功后返回 强一致性(写缓存与写数据库在同一事务边界)
Write-Behind — 写操作只更新缓存,异步批量写入数据库 最终一致性,吞吐量高,但存在数据丢失风险
适用场景: Read-Through + Write-Through 适合对一致性要求高、写频率适中的场景(如用户账户余额)。Write-Behind 适合写密集型、可容忍短时间不一致的场景(如访问计数、日志聚合)。
Read-Through / Write-Through 架构 应用服务 缓存抽象层 (Read/Write Through) 数据库 Read-Through 读流程: ① 读请求 ② 缓存未命中 ③ 加载数据库 ④ 写入缓存 ⑤ 返回数据 Write-Through 写流程: ① 写请求 ② 同步写数据库 ③ 写入缓存 ④ 返回成功

2.3 TTL与过期策略

TTL(Time To Live)是缓存一致性的最后一道防线。即使业务逻辑出现遗漏,合理的过期策略也能自动恢复一致性。

2.3.1 TTL 设置原则

  • 短 TTL(秒级~分钟级): 适用于频繁变化的数据(如实时库存、在线状态),一致性要求高。
  • 中 TTL(分钟级~小时级): 适用于变化较慢的数据(如商品详情、用户资料),平衡命中率与一致性。
  • 长 TTL(天级): 适用于几乎不变的数据(如配置信息、字典表),需配合主动失效机制。
经验值: 对于大多数业务缓存,建议 TTL 设置在 5~30 分钟之间。过短的 TTL 会导致缓存命中率下降,增加数据库压力;过长的 TTL 会放大不一致窗口。

2.3.2 Redis 过期策略

Redis 采用 定期删除 + 惰性删除 的组合策略,兼顾 CPU 与内存效率。

策略 触发时机 行为 优缺点
惰性删除 每次访问 key 时 检查 key 是否过期,过期则删除并返回 nil CPU 友好,但过期 key 可能长期占用内存
定期删除 每 100ms 随机抽查 从设置了过期时间的 key 集合中随机抽取一批,删除其中过期的 key 平衡内存与 CPU,但无法保证即时清理
内存淘汰 内存达到 maxmemory 时 根据淘汰策略(LRU/LFU/TTL/Random)移除 key 防止 OOM,但可能误删未过期 key
# 设置 TTL 示例(单位:秒)
SET user:1001:session "abc123" EX 3600   # 1小时后过期
EXPIRE user:1001:session 3600             # 对已有 key 设置 TTL
TTL user:1001:session                     # 查看剩余存活时间

# 内存淘汰策略配置(redis.conf)
maxmemory 2gb
maxmemory-policy allkeys-lru              # 常用:最近最少使用淘汰
注意: 依赖 TTL 自动恢复一致性时,必须考虑 过期时间窗口。例如,数据库更新后缓存 TTL 还剩 10 分钟,这 10 分钟内应用会读到旧数据。对于强一致性场景,应主动删除或更新缓存,而非仅依赖 TTL。

2.3.3 过期策略对一致性的影响

  • 惰性删除 可能导致过期 key 在未被访问前一直存在,增加不一致窗口。
  • 定期删除 无法保证过期 key 被立即清理,但结合 TTL 上限,不一致时间可控。
  • 内存淘汰 在内存压力下可能淘汰未过期的热 key,导致缓存击穿,需配合缓存预热或互斥锁。

在实际系统中,建议将 TTL 作为 兜底机制,主动失效(删除/更新缓存)作为 主要手段,两者结合才能达到高可用与最终一致性的平衡。