第2章:缓存抽象层的系统定位:分层、边界与交互
缓存技术选型从单一 Redis 演进到多级缓存(本地 + 分布式)、多供应商混合部署时,业务代码会散落大量缓存访问逻辑,导致基础设施耦合、故障扩散、扩展困难。本章聚焦缓存抽象层的系统定位:它在业务层与具体缓存基础设施之间如何插入,职责边界如何划分,以及与数据访问层的交互关系。
2.1 为什么需要插入一个统一的接口层
没有抽象层时,业务代码直接依赖 Jedis、Lettuce、Redisson 或 Memcached Client。这种模式带来三个核心痛点:
| 痛点 | 具体表现 | 直接后果 |
|---|---|---|
| 基础设施耦合 | 业务方法内直接创建 Redis 连接、调用 set/get 方法 | 更换缓存中间件时需要修改几乎所有业务模块 |
| 故障扩散 | 缓存连接中断或超时时,业务线程被阻塞或抛出异常 | 缓存不可用导致业务整体不可用 |
| 缺少治理点 | 无法统一做序列化、压缩、监控、限流、降级 | 指标分散,运维困难,容量规划无依据 |
缓存抽象层以接口为中立的契约,业务层只依赖该接口,具体实现在运行时注入。该模式来自经典的 依赖倒置原则:高层模块(业务)不应依赖低层模块(缓存驱动),双方都应依赖抽象。
核心价值: 抽象层将缓存中间件变成可替换的“后端实现”,业务代码无需感知底层是 Redis、Memcached 还是本地 ConcurrentHashMap。
2.2 职责边界划分
缓存抽象层不是万能中间层,必须清晰界定它做什么和它不做什么。下表列出三层职责边界:
| 层级 | 职责范围 | 不负责的事项 |
|---|---|---|
| 业务层 | 定义缓存键(Key)、理解缓存数据含义、决定缓存策略(缓存什么、何时失效) | 不连接任何缓存的细节,不处理序列化 |
| 缓存抽象层 | 提供泛化的 get/set/delete/expire 接口;管理序列化与反序列化;实现降级、熔断、监控统计;控制批量操作;统一异常处理 | 不解释业务语义,不触发缓存反转,不参与事务边界 |
| 缓存基础设施 | 实际数据存储与过期驱逐;网络连接池管理;哨兵/集群拓扑维护;持久化 | 不理解数据结构含义,不负责业务降级逻辑 |
架构原则: 抽象层对业务暴露的接口必须是“缓存原语”(get/set/delete),而不是“业务行为”(getUserById)。业务行为编排留在业务层。
2.3 与数据访问层(DAO)的交互关系
数据访问层(DAO/Repository)通常负责关系数据库或 NoSQL 的主数据读写。缓存抽象层与 DAO 的典型交互遵循 旁路缓存模式(Cache-Aside):
- 读路径:业务层调用抽象层 get → 未命中 → 业务层调用 DAO 加载数据 → 业务层调用抽象层 set 写入缓存 → 返回。
- 写路径:业务层先调用 DAO 更新持久化存储 → 再调用抽象层 delete 删除缓存(或更新缓存)。
控制流始终由业务层编排,抽象层不主动触发 DAO 调用。这保持了缓存层与数据存储层的信任链路隔离:缓存层不应被当作“可回写的数据存储”,它只做临时加速。
风险提醒: 部分设计让抽象层内嵌“缓存未命中时自动调用 DAO”的逻辑。这会引入循环依赖和延迟膨胀,使抽象层职责不清。除非在特殊场景(如完全读透),否则应当避免。
2.4 系统分层架构与数据流
下面的 SVG 图展示缓存抽象层在整体架构中的夹层位置,以及读写两路的典型数据流与控制流:
2.5 控制流与信任链路
控制流从业务层出发,依次穿透抽象层,最终到达缓存基础设施或数据访问层。关键信任链路约束如下:
- 信任方向:业务层信任抽象层接口稳定;抽象层信任基础设施的连接能力;业务层同时信任 DAO 的数据完整性。
- 降级解耦:抽象层内部捕获基础设施异常后,可将降级决策直接返回给业务层(如返回 null 或兜底缓存),业务层自行决定是否回源 DAO。抽象层不应在降级时主动调用 DAO。
- 超时隔离:抽象层为每个缓存操作为单独的超时控制(如 10ms),防止缓存阻塞影响业务主链路。
信任链路原则: 抽象层对缓存基础设施故障保持“有损服务”态度——宁可降级穿透,也不让业务阻塞等待。
2.6 选型权衡:接口粒度与泛化
抽象层的接口设计有两派策略,适用于不同场景:
| 策略 | 接口粒度 | 典型接口 | 适配场景 | 风险点 |
|---|---|---|---|---|
| 薄接口 | 仅暴露原语 | get, set, delete, expire | 通用缓存加速,多供应商切换 | 无法表达缓存特有功能(如 Redis 的 LREM、SCAN) |
| 厚接口 | 包含少量集合操作 | multiGet, multiSet, atomicIncr | 需要批量操作或原子递增的场景 | 抽象层膨胀,部分基础设施无法实现 |
| 泛化接口 | 完全与基础设施耦合 | execute(RedisCommand) | 需要利用特定缓存所有特性的场景 | 失去抽象意义,退化为“透传壳” |
推荐组合: 以薄接口为主体,在抽象层扩展点中预留
CommandCallback 允许业务绕过抽象层执行特定指令,但需要通过独立 SPI 声明。
2.7 落地步骤与验收标准
落地步骤
- 定义接口契约:先设计
CachePort接口(get/set/delete/expire),使用泛型<V>避免序列化泄漏。 - 实现桩模块:为一个缓存基础设施(如本地 HashMap)提供实现,用于单元测试与原型验证。
- 集成真实实现:实现 Redis 或 Memcached 适配器,将序列化逻辑放入抽象层。
- 嵌入测点与降级:在抽象层实现类中注入监控(命中率、延迟、错误计数)和降级规则。
- 业务迁移:将业务层原有的直接缓存调用替换为依赖
CachePort接口。
验收标准
- 业务层代码零直接引用 Redis/Memcached 客户端库。
- 替换缓存基础设施时(如从单机 Redis 切换到集群 Redis),只修改依赖注入配置和实现 jar,不修改业务代码。
- 缓存基础设施宕机时,业务接口返回降级数据(如空列表或本地缓存),不抛出连接异常。
- 抽象层接口的延迟 P99 低于纯客户端调用延迟的 5%(微乎其微的损耗)。
2.8 风险点
- 过度抽象:接口太泛,丢失缓存特有功能导致性能倒退。
- 降级风暴:降级时大量回源 DAO,可能压垮数据库,需要配合限流。
- 序列化陷阱:抽象层统一序列化可能忽略特定缓存的压缩协议,如 Redis 的 protobuf vs JSON。
- 配置不一致:多缓存实例时,抽象层路由配置与基础设施拓扑不同步造成数据错乱。
缓存抽象层的正确系统定位是“夹层的隔离层与治理层”,不是全能网关,也不是透明代理。本章边界划分为后续实现提供了清晰的模块责任。