核心权衡点:锁的量级与性能损耗的权衡

选择哪种并发控制或一致性手段,本质是在 一致性强度系统吞吐 / 延迟 之间做交易。不存在普适最优解,只有基于回源成本、并发量级和跨实例协调代价的按需组合

  • 量级轻 → 延迟低,但保护弱:进程内 singleflight,非阻塞软标记,纯 TTL 过期。
  • 量级重 → 一致性强,但代价高:跨实例分布式锁,强同步写穿透。

下面从“数据流”与“控制面”两个维度梳理关键技术,并给出组合原则。


1. 数据一致性模式(如何同步缓存与数据库)

这些模式关注当源数据变更时,缓存如何更新或失效,不直接涉及并发控制,但影响选型。

Cache Aside(旁路缓存)

  • 模式:读未命中则查 DB 并回写缓存;写直接更新 DB,然后删除缓存。
  • 锁量级:写操作无锁,仅单一 DEL;读可配合 singleflight 防击穿。
  • 权衡:最终一致窗口 = 删缓存到下次重建之间;删除失败需重试或 TTL 兜底。
  • 适用:读多写少,可接受短暂不一致的场景(大多数互联网业务)。

Read/Write Through(读写穿透)

  • 模式:缓存层代理所有 DB 读写,业务只与缓存交互。
  • 锁量级:同步写,需保证缓存与 DB 的原子更新,往往引入分布式锁或事务消息。
  • 权衡:一致性强,但每次写都要同时操作缓存+DB,写入延迟高,实现复杂。

Write Behind(异步回写)

  • 模式:写只更新缓存,异步批量刷回 DB。
  • 锁量级:缓存写入轻量,但需要队列/日志保证不丢数据。
  • 权衡:写入性能极高,一致性很弱,允许丢数据窗口。

延迟双删与订阅刷新

  • 延迟双删:写 DB 前先删缓存,DB 更新完成后延迟再删一次(用于规避主从延迟导致的不一致)。
  • Binlog 订阅(如 Canal):异步监听 DB 变更流水,精确删除或更新缓存。
  • 锁量级:均为异步,无额外锁竞争,但引入消息延迟和架构复杂度。

本系统选择Cache Aside + MQ 异步删除,利用已有死信队列保障最终一致性,兼顾简单和可靠。


2. 并发控制机制(如何防击穿、防并发重建)

singleflight(进程内请求合并)

  • 量级:极轻,无网络开销,仅内存 map + 阻塞等待。
  • 保护范围:单进程内相同请求的合并。
  • 性能损耗:少数请求等待首次执行完毕,延迟可忽略。
  • 适用:高并发下同一批缓存键临时穿透 Redis 的场景。

分布式锁(如 Redis SET NX EX)

  • 量级:重,涉及网络往返、轮询等待、锁 TTL 管理和 Lua 释放。
  • 保护范围:跨实例互斥,确保单点重建。
  • 性能损耗:锁竞争导致额外延迟(轮询 100ms+),可能成为瓶颈。
  • 适用:回源成本极其高昂且要求强一致的场景(如复杂报表、详情缓存重建)。

软标记 / SETNX 提示(非阻塞跨实例通知)

  • 量级:极轻,一次 Redis SETNX 无等待。
  • 保护范围:跨实例“知情权”,让其他实例主动降级而非常规等待。
  • 性能损耗:基本无延迟,仅需判断标记存在与否。
  • 适用:重建允许降级或短暂不一致的场景(批量读、冷缓存预热)。

仅靠 TTL

  • 量级:无。
  • 保护:无任何并发控制。
  • 损耗:零,但可能发生缓存击穿或雪崩。
  • 适用:数据可大量容忍陈旧,或变更极低频。

3. 组合决策矩阵

场景回源成本推荐组合理由
视频实体批量读取(高频、可降级)中(批量主键查询)singleflight + 软标记进程内去重 + 跨实例非阻塞防多余穿透
视频详情页读取(中频、不期望旧数据)中高(复杂 SQL 或关联查询)分布式锁 + double-check强控单实例重建,避免多实例重复计算
关注流冷缓存重建(低频、允许最终一致)高(多表聚合排序)软标记 + 单一执行不强制等待,接受少量重复重建成本优于锁等待
写操作后缓存失效(Cache Aside)极低(DEL 命令)无需锁,直接 DEL并发写由 DB 串行化,缓存仅打扫战场

4. 一句话选型指南

  • 先上 singleflight,解决绝大多数进程内并发穿透。
  • 多实例部署且重建轻量时,补 软标记 提示降级,避免引入锁。
  • 只有当“多个实例并发重建会造成无法接受的成本或数据矛盾”时,再引入 分布式锁
  • 缓存更新一律走 Cache Aside 异步删除,放弃复杂同步,靠重试和 TTL 兜底。

记住:锁是最后的手段,不是默认选项。 能靠最终一致和降级解决的,不要用强同步去惩罚高并发。