1.1 缓存契约概念

1.1.1 什么是缓存契约

缓存契约(Cache Contract)是分布式系统中缓存客户端与缓存服务端之间的一组显式约定,它定义了数据的存储格式、过期策略、序列化方式、版本标识以及一致性保证。契约不是代码实现,而是行为与结构的规范,确保不同团队、不同服务对同一份缓存数据的读写行为达成一致。

核心定义:缓存契约 = 数据结构约定 + 生命周期规则 + 版本标识 + 访问协议

在领域驱动设计(DDD)视角下,缓存契约对应限界上下文(Bounded Context)之间的数据交换协议。每个限界上下文拥有自己的缓存空间,契约明确了哪些数据可以跨上下文共享、以何种格式共享、何时失效。

契约维度 说明 Redis 中的体现
数据结构 缓存值的类型、字段、嵌套关系 String / Hash / JSON 序列化
序列化 对象与字节流的转换规则 JSON / Protobuf / MessagePack
过期策略 TTL、最大空闲时间、主动淘汰 EXPIRE / TTL / LRU
版本标识 数据版本号或时间戳 自定义 version 字段
一致性级别 最终一致性 / 读写一致性 / 强一致性 Redis 主从 + 哨兵

1.1.2 契约在分布式系统中的角色

分布式系统中,缓存契约承担以下关键角色:

  1. 解耦生产者与消费者:数据写入方(生产者)与读取方(消费者)通过契约约定格式,无需感知对方内部实现。
  2. 版本兼容保障:契约中的版本字段允许系统同时存在多个数据版本,支持灰度发布与回滚。
  3. 故障隔离:当缓存数据格式异常时,契约校验可以快速拒绝无效数据,防止脏数据扩散。
  4. 性能优化边界:契约定义了哪些数据适合缓存、缓存多久,避免缓存滥用导致内存膨胀。
  5. 领域边界映射:每个限界上下文维护自己的缓存契约,防止跨上下文的数据耦合。
实践建议:在微服务架构中,每个服务应独立声明其缓存契约,并通过共享库(Shared Kernel)或契约测试(Contract Testing)保证一致性。避免在多个服务间直接共享 Redis Key 前缀或序列化配置。

1.1.3 契约与缓存的关联

缓存与契约的关系可以概括为“无契约,不缓存”。没有显式契约的缓存系统会逐渐退化为不可维护的“数据沼泽”:

  • 格式漂移:不同版本的代码写入不同格式的数据,读取时无法反序列化。
  • 过期混乱:部分数据 TTL 过长,部分过短,导致缓存命中率波动。
  • 版本冲突:旧版本服务写入旧格式数据,新版本服务读取后产生异常。
  • 领域泄漏:一个上下文的内部数据被另一个上下文误读,破坏封装性。

缓存契约通过以下机制建立关联:

关联维度 缓存侧 契约侧
写入 序列化 + 设置 TTL 校验版本号、字段完整性
读取 反序列化 + 命中判断 校验版本兼容性、字段类型
失效 主动删除 / TTL 过期 触发版本升级或数据迁移
监控 缓存命中率、内存使用 契约违反次数、版本分布
缓存契约在分布式系统中的角色与关联 缓存契约 生产者(写入方) 消费者(读取方) Redis 缓存层(存储 + 过期) 限界上下文 A / 限界上下文 B / 共享内核 契约定义数据格式与版本 契约校验保证兼容性

1.1.4 缓存契约的版本化

版本是缓存契约的核心要素。每次数据结构变更(新增字段、修改类型、删除字段)都应伴随版本号递增。版本化策略通常有两种:

策略 实现方式 适用场景 风险
内联版本 在缓存值中嵌入 version 字段 单服务、小团队 版本膨胀后解析复杂
外置版本 使用独立的 Redis Key 存储版本映射 多服务、跨团队 额外查询开销
混合版本 内联主版本 + 外置兼容性矩阵 大规模分布式系统 运维复杂度高
常见陷阱:不要将版本号与业务数据混在一起作为 Redis Key 的一部分(如 user:123:v2)。这会导致旧版本数据无法被自动清理,且 Key 数量膨胀。正确的做法是将版本号放在 Value 内部或使用独立的版本映射 Key。

1.1.5 契约驱动的缓存设计原则

  1. 显式声明:每个缓存 Key 的契约必须在代码中显式声明,禁止隐式约定。
  2. 向前兼容:新版本契约必须能读取旧版本数据,旧版本代码遇到新版本数据应优雅降级。
  3. 最小化共享:跨限界上下文的缓存数据应尽量少,优先使用事件驱动同步而非共享缓存。
  4. 契约即文档:缓存契约应自动生成文档,供上下游团队查阅。
  5. 可测试性:每个缓存契约都应配套单元测试和契约测试,验证序列化/反序列化正确性。
缓存契约版本化流程 定义契约 v1 写入缓存 读取校验 版本匹配 契约变更 v2 双写兼容 灰度切换 废弃 v1 版本升级期间:旧版本数据继续可读,新版本数据逐步写入,最终完成迁移

1.1.6 代码示例:缓存契约定义

以下是一个基于 Redis Hash 的缓存契约示例,包含版本标识和序列化校验:

// 缓存契约定义(Java 示例)
public class UserCacheContract {
    // 当前契约版本
    public static final int CURRENT_VERSION = 2;

    // 缓存数据结构
    public static class UserCacheValue {
        private int version;
        private String userId;
        private String userName;
        private String email;
        private long lastUpdated;

        // 序列化方法
        public Map<String, String> toHash() {
            Map<String, String> hash = new HashMap<>();
            hash.put("version", String.valueOf(CURRENT_VERSION));
            hash.put("userId", userId);
            hash.put("userName", userName);
            hash.put("email", email);
            hash.put("lastUpdated", String.valueOf(lastUpdated));
            return hash;
        }

        // 反序列化 + 版本校验
        public static UserCacheValue fromHash(Map<String, String> hash) {
            int version = Integer.parseInt(hash.get("version"));
            if (version > CURRENT_VERSION) {
                // 遇到未来版本,降级处理
                throw new CacheContractException("未知契约版本: " + version);
            }
            // 版本兼容转换逻辑
            UserCacheValue value = new UserCacheValue();
            value.version = version;
            value.userId = hash.get("userId");
            value.userName = hash.get("userName");
            value.email = hash.getOrDefault("email", ""); // 兼容旧版本无 email 字段
            value.lastUpdated = Long.parseLong(hash.get("lastUpdated"));
            return value;
        }
    }
}
关键点:上述代码中 fromHash 方法使用 getOrDefault 处理旧版本缺失字段,实现了向前兼容。版本号 CURRENT_VERSION 作为常量集中管理,避免散落在业务代码中。