第四章 两级缓存的模块分层与组件职责
两级缓存架构的核心不在于缓存算法的先进程度,而在于将缓存能力拆解为四个职责清晰的模块:客户端代理层、L1 管理组件、L2 管理组件和一致性协调器。四者共同构成从请求入口到数据落地的完整链路,任何模块的职责漂移都会导致架构退化。
4.1 架构边界与分层
四个模块按照请求处理顺序与数据温度两个维度进行分层。边界划分的核心原则是:上层无状态,下层有状态;上层依赖下层接口,下层不反向依赖。
- 客户端代理层——无状态网关,负责协议适配、路由分发、故障屏蔽。不持有任何缓存数据。
- L1 管理组件——进程内或近端热数据层,容量小(通常 GB 级别),延迟极低(微秒级)。数据生命周期短,由一致性协调器驱动失效。
- L2 管理组件——分布式冷/温数据层,容量大(TB 级别),延迟在毫秒级。作为 L1 的后备和逐出数据的归宿。
- 一致性协调器——旁路控制面,不参与数据面 I/O。负责监听数据变更、生成失效事件、协调 L1 与 L2 之间的数据版本。
4.2 组件职责详解
下表从职责边界、数据载体、核心操作、故障影响四个维度对齐四个组件,防止职责重叠或遗漏。
| 组件 | 职责边界 | 数据载体 | 核心操作 | 故障影响范围 |
|---|---|---|---|---|
| 客户端代理层 | 协议适配、请求路由、熔断降级 | 无状态(不持有缓存数据) | 代理转发、健康检测、流量分发 | 全局请求中断 |
| L1 管理组件 | 热数据快速查询、本地失效处理 | 堆内/堆外内存(如 caffeine、ehcache) | get/set/evict/失效订阅 | 仅影响热数据命中率,L2 可兜底 |
| L2 管理组件 | 全量缓存存储、持久化、逐出策略 | 分布式 KV 存储(如 redis、memcached) | get/set/delete/scan | L1 逐出后 miss 会穿透至 DB |
| 一致性协调器 | 变更监听、失效事件生产、版本仲裁 | 事件日志或消息队列 | publish/subscribe/ack | L1 与 L2 数据不一致 |
4.2.1 客户端代理层
该层通常是 API 网关或 Sidecar 代理。它不关心数据存在 L1 还是 L2,只按照路由规则将请求分发到对应的缓存管理组件。同时负责优雅降级:当 L1 或 L2 连续超时时,自动摘除异常节点并返回降级响应。
4.2.2 L1 管理组件
部署在应用进程内或同一宿主机,使用堆内缓存(如 Caffeine)或堆外内存。核心指标是命中率和失效延迟。L1 必须订阅一致性协调器的失效事件,在收到失效通知后立即驱逐本地条目,不可等待 TTL 自动过期。
4.2.3 L2 管理组件
独立部署的分布式缓存集群,通常使用 Redis Cluster 或 Memcached。L2 的职责是兜底 L1 的 miss 以及接收 L1 逐出的冷数据。L2 需要配置合理的逐出策略(LRU/LFU/TTL),防止内存写满导致频繁的 DB 穿透。
4.2.4 一致性协调器
作为旁路控制面,协调器监听数据变更事件(如 binlog、MQ 消息),生成失效事件并推送至所有 L1 节点。协调器自身不缓存数据,但需要维护事件序号用于版本仲裁。典型实现包括 Redis Pub/Sub、Kafka、自研事件总线。
4.3 数据流与控制流
理解数据流与控制流的分离是掌握两级缓存架构的关键。数据流走 L1 → L2 → DB 路径,控制流走协调器 → L1 路径。
读路径
- 客户端请求 → 客户端代理层 → L1 管理组件;
- L1 命中 → 直接返回(微秒级);
- L1 miss → 转发至 L2 管理组件;
- L2 命中 → 返回数据,L1 异步回填(毫秒级);
- L2 miss → 穿透至数据源,L2 回填后返回。
写路径
- 客户端写入请求 → 客户端代理层 → 一致性协调器;
- 协调器生成失效事件并广播至所有 L1 节点;
- 协调器将数据写入 L2 管理组件(或委托写入);
- L1 节点收到失效事件后驱逐对应条目;
- 下次读请求触发 L1 miss → 回填最新数据。
控制流
- 失效事件流:协调器 → L1 管理组件(异步、至少一次投递);
- 逐出流:L1 管理组件 → L2 管理组件(同步或异步,取决于逐出策略);
- 健康探测流:客户端代理层 → L1/L2 管理组件(心跳检测)。
4.4 信任链路
信任链路定义了组件之间的数据可信假设,决定了架构是最终一致性还是强一致性。
| 信任关系 | 假设 | 风险 |
|---|---|---|
| 客户端代理层 → L1/L2 | 代理层信任 L1/L2 返回的数据是最新版本 | 若协调器失效,L1 可能返回脏数据 |
| L1 → 一致性协调器 | L1 信任协调器推送的失效事件是完整且有序的 | 事件丢失或乱序导致 L1 残留过期数据 |
| L2 → 数据源 | L2 信任数据源为权威数据源 | 数据源回填延迟时 L2 可能提供旧版本 |
| 一致性协调器 → L2 | 协调器信任 L2 已成功写入后再广播失效 | 协调器与 L2 之间网络分区导致写后失效未送达 |
4.5 选型权衡
| 决策点 | 选项 A | 选项 B | 推荐场景 |
|---|---|---|---|
| L1 实现方式 | 堆内缓存(Caffeine) | 堆外内存(OHC) | 延迟敏感选堆内;GC 压力大选堆外 |
| L2 存储 | Redis Cluster | Memcached | 需持久化/复杂结构选 Redis;纯 KV 选 Memcached |
| 一致性协调器 | Redis Pub/Sub | Kafka | 小规模选 Redis Pub/Sub;大规模选 Kafka |
| 失效策略 | 同步失效 | 异步失效 | 强一致性选同步;高可用选异步 |
| L1 逐出策略 | LRU | LFU | 热点集中选 LFU;通用场景选 LRU |
4.6 风险点
- 协调器单点故障:若协调器宕机,L1 无法接收失效事件,导致脏数据持续服务。必须部署多副本或使用 RAFT 协议保证高可用。
- 失效事件风暴:批量数据更新时,协调器瞬间产生大量失效事件,可能压垮 L1 节点的事件处理线程。需引入事件合并窗口(如 100ms 内相同 key 的失效事件合并为一次)。
- L1 与 L2 版本回退:协调器事件乱序导致 L1 用旧版本覆盖新版本。解决方案是为每个 key 附加单调递增版本号,L1 只接受版本号不低于当前值的条目。
- 客户端代理层过载:代理层同时承担路由和降级逻辑,容易成为 CPU/内存瓶颈。代理层应使用异步非阻塞模型,并独立部署。
4.7 落地步骤
- 第 1 步:模块边界对齐——与团队对齐四个组件的职责边界,输出《组件职责矩阵》,明确每个组件的 input、output、fail-mode。
- 第 2 步:L2 先行——先部署 L2 管理组件(Redis Cluster),应用直连 L2,验证基础缓存链路。
- 第 3 步:引入客户端代理层——在 L2 之前加入代理层,将路由逻辑从应用中剥离,验证代理层的流量分发和故障屏蔽能力。
- 第 4 步:L1 局部试点——选择 1~2 个非核心业务接入 L1 管理组件,使用 Caffeine 作为本地缓存,观察命中率和 GC 影响。
- 第 5 步:一致性协调器上线——部署协调器,连接 L1 和 L2,验证失效事件的投递延迟和覆盖率。
- 第 6 步:全量切换与灰度——逐步将流量从直连 L2 切换到两级缓存链路,每个步骤设置可观测性指标和回滚方案。
4.8 验收标准
| 验收维度 | 验收指标 | 目标值 |
|---|---|---|
| 模块隔离性 | 任一组件宕机不影响其他组件独立运行 | L1 宕机时 L2 仍可服务;协调器宕机时 L2 仍可读 |
| 职责清晰度 | 组件间无重叠职责,无跨层调用 | 代码审计 0 例越权调用 |
| 数据一致性 | L1 与 L2 数据不一致窗口 | ≤ 100ms(异步失效场景) |
| 延迟衰减 | L1 命中相比直连 L2 的延迟降低 | ≥ 80% 的请求延迟降低 50% 以上 |
| 协调器吞吐 | 单协调器节点支持的失效事件峰值 | ≥ 50,000 events/s |
| 故障恢复 | 协调器主备切换时长 | ≤ 5s |
完成以上验收标准后,四个模块的分层与职责即达到生产级要求。下一阶段将围绕 L1 与 L2 之间的数据同步策略进行深度设计,确保在写多读少、热点倾斜等极端场景下仍然保持稳定。