3. 缓存基础与失效模式:缓存工作原理(Redis/Memcached)、常见失效模式(穿透、击穿、雪崩)、缓存一致性问题的根源
在 CI/CD 管道中引入缓存是为了加速构建、减少依赖下载、复用制品。但缓存并非银弹——理解其底层工作原理与失效模式,是设计高可用、高一致缓存策略的前提。本章从两大主流缓存引擎(Redis 与 Memcached)的核心差异出发,深入剖析三种经典失效场景,并揭示缓存一致性问题的本质根源。
3.1 缓存引擎工作原理对比:Redis vs Memcached
Redis 与 Memcached 是生产环境最常用的分布式缓存。下表从数据结构、持久化、集群模式等关键维度进行对比:
| 对比维度 | Redis | Memcached |
|---|---|---|
| 数据结构 | String、Hash、List、Set、Sorted Set、Bitmap、HyperLogLog、Stream | 仅 Key-Value(String) |
| 持久化 | RDB 快照 + AOF 日志,支持数据恢复 | 无持久化,纯内存 |
| 集群模式 | Redis Cluster(分片 + 主从)、Sentinel(高可用) | 客户端分片(一致性哈希),无原生集群 |
| 过期策略 | 惰性删除 + 定期删除 + 内存淘汰(8 种策略) | 惰性删除 + LRU 淘汰 |
| 典型场景 | 会话缓存、排行榜、分布式锁、消息队列、CI 构建缓存 | 简单 KV 缓存、数据库查询结果缓存、Session 存储 |
| CI/CD 适用性 | 高(支持复杂数据结构、持久化、原子操作) | 中(适合纯 KV 场景,无持久化风险) |
CI/CD 选型建议: 若管道需要缓存构建中间产物(如 Maven .m2、npm node_modules)并支持增量更新,优先选择 Redis 的 Hash 或 String 结构 + 持久化;若仅缓存静态依赖元数据(如包版本列表),Memcached 的简单 KV 模型即可满足,且内存开销更低。
3.2 缓存读写基本流程
无论使用哪种引擎,缓存读写的标准流程可抽象为以下步骤:
- 读请求到达: 应用先查询缓存(Cache-Aside 模式)。
- 缓存命中: 直接返回缓存数据,跳过数据库/后端服务。
- 缓存未命中: 回源查询数据库/后端,将结果写入缓存并设置 TTL,再返回给调用方。
- 写请求到达: 先更新数据库,再删除或更新缓存(延迟双删、最终一致)。
这个看似简单的流程,在并发、高负载、网络分区等条件下会衍生出三种经典失效模式。
3.3 常见失效模式:穿透、击穿、雪崩
三种失效模式是缓存治理中最核心的风险点。下表从定义、触发条件、影响范围三个维度进行对比:
| 失效模式 | 定义 | 触发条件 | 影响范围 |
|---|---|---|---|
| 缓存穿透 | 请求查询一个根本不存在的数据(Key 既不在缓存也不在数据库) | 恶意攻击、非法参数、查询不存在的 ID | 数据库直接承受所有请求,可能导致连接池耗尽 |
| 缓存击穿 | 一个热点 Key 在缓存失效的瞬间,大量并发请求同时回源查询数据库 | 热点数据过期、高并发场景 | 单个数据库节点压力骤增,可能引发局部故障 |
| 缓存雪崩 | 大量缓存 Key 在同一时间集中失效,导致所有请求全部回源 | 统一 TTL 设置、缓存节点宕机、重启后缓存清空 | 数据库整体被压垮,服务大面积不可用 |
3.3.1 缓存穿透
穿透的本质是“空查询”绕过缓存直达数据库。在 CI/CD 管道中,典型场景是构建脚本请求一个不存在的制品版本号,或恶意扫描不存在的依赖包名。
治理策略:
- 缓存空对象: 对查询结果为 null 的 Key 也进行缓存(TTL 较短,如 60s),防止重复穿透。
- 布隆过滤器(Bloom Filter): 在缓存层前置一个布隆过滤器,快速判断 Key 是否存在,不存在则直接拒绝。
- 参数校验: 在入口层对请求参数进行合法性校验,拦截明显无效的查询。
3.3.2 缓存击穿
击穿只针对单个热点 Key。例如 CI 管道中某个公共基础镜像的元数据 Key,在过期瞬间被数百个构建任务同时请求。
治理策略:
- 互斥锁(Mutex Key): 在回源查询时加分布式锁,只允许一个线程去数据库加载数据,其他线程等待或重试。
- 逻辑过期: 缓存中存储数据的同时存储逻辑过期时间,后台异步线程负责刷新,避免热点 Key 在高峰期失效。
- 永不过期 + 主动更新: 对热点 Key 不设置 TTL,由后台定时任务或消息队列驱动更新。
3.3.3 缓存雪崩
雪崩是击穿的放大版。当大量 Key 同时失效,或缓存节点整体宕机时,数据库会瞬间被海量请求淹没。在 CI/CD 场景中,若所有构建任务的依赖缓存都设置了相同的 TTL(如 1 小时),整点时刻就会触发雪崩。
治理策略:
- TTL 随机化: 在基础 TTL 上增加随机偏移(如 ±20%),避免 Key 集中失效。
- 缓存高可用: 使用 Redis Sentinel 或 Cluster 模式,避免单点故障。
- 限流降级: 在数据库层配置连接池限流,当缓存失效时,对回源请求进行排队或直接返回降级数据。
- 本地缓存(L1 Cache): 在应用进程内增加一级本地缓存(如 Caffeine),作为 Redis 的补充,减少对远程缓存和数据库的冲击。
3.4 缓存一致性问题的根源
缓存一致性问题的本质是:数据库状态与缓存状态在时间窗口内出现偏差。在 CI/CD 管道中,这种偏差会导致构建任务使用过期的依赖版本、错误的构建参数或已废弃的制品。
3.4.1 一致性问题产生的三个核心原因
| 原因 | 说明 | CI/CD 典型场景 |
|---|---|---|
| 并发写冲突 | 多个线程/进程同时更新数据库和缓存,操作顺序不一致导致缓存中残留旧数据 | 多个构建任务同时更新同一个依赖缓存 Key |
| 操作顺序颠倒 | 先更新缓存再更新数据库,或先删除缓存再更新数据库,在失败时导致数据不一致 | 管道脚本中先 set 缓存再写数据库,数据库写入失败后缓存已污染 |
| 网络与超时 | 缓存操作或数据库操作因网络超时而部分成功,导致两端状态不同步 | Redis 写入成功但数据库写入超时,或反之 |
3.4.2 一致性的分类与权衡
根据 CAP 理论和实际业务需求,缓存一致性可分为三个级别:
- 强一致性: 每次读操作都能读到最新的写入结果。实现代价极高,通常需要分布式事务或 2PC,不适合高并发缓存场景。
- 最终一致性: 允许短暂的不一致窗口,但保证经过一定时间后数据最终一致。这是缓存系统最常用的模型。
- 弱一致性: 不保证任何时间点的一致性,适用于对实时性无要求的场景(如历史日志缓存)。
CI/CD 管道中的一致性选择: 构建缓存通常接受最终一致性(秒级延迟),因为构建任务对依赖版本的一致性要求通常不高于“同一构建任务内一致”。但对于发布流水线中的制品版本缓存,必须使用强一致性或通过版本号校验来避免不一致。
3.4.3 一致性问题根源的抽象模型
从系统架构视角看,缓存一致性问题的根源可抽象为以下模型:
3.5 本章小结
- Redis 与 Memcached 在数据结构、持久化、集群模式上差异显著,CI/CD 场景推荐 Redis 以支持复杂缓存策略。
- 缓存穿透、击穿、雪崩 是三种必须隔离治理的失效模式,分别对应空查询、热点 Key 失效、大规模 Key 集中失效。
- 缓存一致性问题的根源 在于并发写冲突、操作顺序颠倒和网络超时,治理方向是最终一致性模型 + 补偿机制。
- 在 CI/CD 管道中,缓存治理的核心目标是:在保证构建速度的同时,将数据不一致窗口控制在可接受范围内。