1.1 多模块项目缓存架构概览:缓存分层、同步需求、边界隔离的核心挑战

在多模块微服务架构中,缓存不再是一个独立组件,而是一套横跨各模块的分层体系。本章从全局视角梳理缓存分层的典型模型、模块间缓存同步的触发条件,以及模块边界隔离的设计约束。理解这三者是后续章节实现一致性协议、选择失效模式、配置缓存中间件的前提。

1.1.1 缓存分层模型

典型的微服务缓存架构自顶向下分为三层:

  • 本地缓存层(L1):每个业务模块内部的进程内存缓存(如 Caffeine、Guava Cache),延迟极低(<1μs),但容量受限、数据不共享。
  • 分布式缓存层(L2):跨模块共享的集中式缓存(如 Redis、Memcached),数据可被多个模块读取,但网络开销(~1ms)和序列化成本不可忽略。
  • 数据库层(L3):持久化存储(MySQL、PostgreSQL),为数据一致性提供最终保障,访问延迟通常 >10ms。

下图展示了三层缓存在一个包含两个业务模块的系统中的关系与同步路径:

多模块项目缓存分层与同步架构 缓存分层与同步架构图 外部请求 业务模块A 业务模块B 本地缓存(L1) 本地缓存(L1) 分布式缓存(L2 · Redis) 数据库(L3) 缓存同步总线(MQ / 变更事件 / RPC 通知)

1.1.2 缓存同步需求

当模块A更新了数据,需要通知其他模块的本地缓存以及分布式缓存层,否则会出现“写后读不一致”。常见的同步触发场景包括:

场景触发动作同步目标
模块内数据变更写操作后主动失效本地键仅本模块本地缓存
模块间数据共享通过消息队列广播变更事件其他模块本地缓存 + 分布式缓存
分布式缓存过期TTL 到期或被驱逐无需主动同步,但模块需回源数据库
数据库直写触发监听 binlog 或 CDC 管道分布式缓存(写回或失效)

1.1.3 模块边界隔离的核心挑战

微服务要求每个模块拥有独立的数据域和缓存空间,但现实中的缓存往往被“无意识共享”,导致以下问题:

  • 缓存键污染:模块A和模块B使用相同的缓存键前缀,导致互相覆盖。解决方案:模块级命名空间隔离(如 moduleA:user:123 vs moduleB:user:123)。
  • 不当的缓存穿透:一个模块的错误查询(如空值未缓存)可能穿透到数据库,拖慢所有模块的DB连接池。
  • 同步风暴:模块间过度广播无效变更事件,导致本地缓存频繁失效,命中率骤降。
  • 序列化协议不一致:模块A使用 JSON,模块B使用 Protobuf,共用 Redis 时反序列化失败。
边界隔离设计原则:每个模块只应操作自己的缓存 namespace,通过统一 API Gateway 和事件总线处理跨模块缓存失效。严禁模块之间直接读写对方缓存命名空间。

1.1.4 常见错误与应对

错误模式后果应对方法
所有模块共用同一 Redis DB 且无键前缀键碰撞、数据混乱配置 keyPrefix = moduleName + ":"
本地缓存未设置最大容量和淘汰策略OOM 或 GC 停顿使用 Caffeine 设置 maximumSize 和 expireAfterWrite
同步事件使用同步 RPC 而非异步消息写请求阻塞、级联超时改为 MQ(RocketMQ/Kafka)异步发布事件
忽略缓存穿透保护数据库压力骤增缓存空值 + Bloom Filter 预检

1.1.5 一个典型的模块缓存配置示例(Spring Boot + Redis)

以下配置展示了如何为每个模块隔离缓存键空间,并开启同步事件监听:

# application-moduleA.yml
spring:
  cache:
    type: redis
    cache-names: moduleA:users, moduleA:products
    redis:
      time-to-live: 600s
  redis:
    namespace: moduleA    # 自动添加前缀

# 同时需要在模块A中配置本地缓存(Caffeine)
# 用于热点数据降级,减少Redis压力
// CacheSyncEventPublisher.java
@Component
public class CacheSyncEventPublisher {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public void notifyInvalidate(String keySpace, String key) {
        // 发布到模块专属主题,其他模块订阅
        String topic = "cache:invalidate:" + keySpace;
        redisTemplate.convertAndSend(topic, key);
    }
}
调试提示:在开发阶段,建议开启 Redis 命令日志(REDIS_DEBUG=true)并观察 keys * 输出中的前缀,快速定位模块间键冲突。

1.1.6 性能与稳定性优化方向

  • 本地缓存使用 Caffeine weakKeys 避免内存泄露,recordStats 监控命中率。
  • 分布式缓存采用 读写分离 + 本地缓存失效通知,减少对 Redis 的重复查询。
  • 模块边界通过 API 版本化(如 /v1/moduleA/cache) 和 缓存事件契约(事件 schema 强制校验) 实现防御式隔离。
  • 部署时每个模块独占 Redis 逻辑数据库(db0~db15) 或 独立 Redis 实例,物理隔离避免相互影响。