31 - 分布式架构契约体系

19大分布式契约接口、Local/Redis双模式切换、启动校验与零改动扩展

一、为什么需要分布式契约层

面试核心考点:如何让一个项目从「单机模式」平滑过渡到「分布式部署」而不需要改动业务代码?答案是抽象契约层

传统做法 vs 契约层做法:

  传统做法 (硬编码):
  ┌─────────────────────────────────────────────┐
  │ Service层直接调用 ConcurrentHashMap          │
  │ → 想换Redis? 改几十个文件!                  │
  │ → 想加分布式锁? 又改几十个文件!             │
  │ → 每次扩展都是大工程                        │
  └─────────────────────────────────────────────┘

  契约层做法 (本项目):
  ┌─────────────────────────────────────────────┐
  │ Service层 → 契约接口 → 具体实现             │
  │                                             │
  │ 业务代码只依赖接口:                         │
  │   @Autowired DistributedLock lock;          │
  │   lock.tryLock("key", 30, TimeUnit.SECONDS) │
  │                                             │
  │ 运行时自动选择实现:                         │
  │   LOCAL  → ReentrantLock (单机内存)         │
  │   REDIS  → RedissonLock (分布式)            │
  │                                             │
  │ 切换只需改一行配置:                         │
  │   distributed.mode=LOCAL → REDIS            │
  └─────────────────────────────────────────────┘

二、19个分布式契约接口

本项目定义了19个契约接口,覆盖分布式系统的所有核心关注点:

#契约接口职责Redis Key 模式
1DistributedLock分布式互斥锁lock:{resource}
2DistributedRateLimiter分布式限流ratelimit:{scope}:{key}
3DistributedCircuitBreaker分布式熔断器cb:{name}:state
4DistributedCounter原子计数器counter:{name}
5DistributedCache分布式缓存cache:{namespace}:{key}
6DistributedQueue分布式队列queue:{name}
7DistributedPubSub发布/订阅channel:{topic}
8DistributedIdGenerator全局唯一ID生成idgen:{biz}
9DistributedSessionStore会话存储session:{channelType}:{userId}
10DistributedDedupeStore消息去重dedup:{channel}:{msgId}
11DistributedLeaderElection主节点选举leader:{group}
12DistributedHealthRegistry健康状态注册health:{nodeId}
13DistributedTaskStore任务状态持久化task:{taskId}:state
14DistributedCheckpointStore断点续传检查点checkpoint:{taskId}:{step}
15DistributedMetricsCollector指标收集/聚合metrics:{name}:{window}
16DistributedConfigStore动态配置中心config:{namespace}:{key}
17DistributedSemaphore分布式信号量sem:{resource}
18DistributedBloomFilter布隆过滤器bloom:{name}
19DistributedScheduler分布式定时调度scheduler:{job}:lock

三、Local vs Redis 双模式设计

双模式实现架构:

  ┌─────────────────────────────────────────────────────────┐
  │                   业务层 (Service)                       │
  │         只依赖接口,完全不感知底层实现                   │
  └───────────────────────┬─────────────────────────────────┘
                          │ @Autowired
                          ▼
  ┌─────────────────────────────────────────────────────────┐
  │              契约接口 (DistributedXxx)                   │
  │      tryLock() / increment() / publish() / ...          │
  └──────────┬──────────────────────────────┬───────────────┘
             │                              │
    @ConditionalOnProperty          @ConditionalOnProperty
    (mode = LOCAL)                  (mode = REDIS)
             │                              │
             ▼                              ▼
  ┌─────────────────────┐       ┌───────────────────────┐
  │  LocalXxxImpl       │       │  RedisXxxImpl         │
  │  · ConcurrentMap    │       │  · RedisTemplate      │
  │  · ReentrantLock    │       │  · Lua Script         │
  │  · AtomicLong       │       │  · Redisson           │
  │  · 单JVM有效        │       │  · 跨节点共享         │
  └─────────────────────┘       └───────────────────────┘

  关键设计原则:
  ┌──────────────────────────────────────────────┐
  │ 1. 接口语义一致:两种实现的行为完全等价      │
  │ 2. 零侵入切换:application.yml 一行改配置    │
  │ 3. 单元测试友好:LOCAL模式无需外部依赖       │
  │ 4. 渐进式迁移:可逐个模块从LOCAL切到REDIS    │
  └──────────────────────────────────────────────┘

Local实现示例

@Component
@ConditionalOnProperty(name = "distributed.mode", havingValue = "LOCAL")
public class LocalDistributedLock implements DistributedLock {

    private final ConcurrentMap<String, ReentrantLock> locks
        = new ConcurrentHashMap<>();

    @Override
    public boolean tryLock(String key, long timeout, TimeUnit unit) {
        ReentrantLock lock = locks.computeIfAbsent(key, k -> new ReentrantLock());
        try {
            return lock.tryLock(timeout, unit);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }

    @Override
    public void unlock(String key) {
        ReentrantLock lock = locks.get(key);
        if (lock != null && lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }
}

Redis实现示例

@Component
@ConditionalOnProperty(name = "distributed.mode", havingValue = "REDIS")
public class RedisDistributedLock implements DistributedLock {

    private final RedissonClient redisson;

    @Override
    public boolean tryLock(String key, long timeout, TimeUnit unit) {
        RLock lock = redisson.getLock("lock:" + key);
        try {
            return lock.tryLock(timeout, 30, unit); // leaseTime=30s防死锁
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }

    @Override
    public void unlock(String key) {
        RLock lock = redisson.getLock("lock:" + key);
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }
}

四、DistributedRuntimeProperties 配置体系

@ConfigurationProperties(prefix = "distributed")
public class DistributedRuntimeProperties {

    /** 运行模式: LOCAL / REDIS */
    private Mode mode = Mode.LOCAL;

    /** Redis配置(mode=REDIS时生效) */
    private RedisConfig redis = new RedisConfig();

    /** 各契约的独立配置 */
    private LockConfig lock = new LockConfig();
    private RateLimitConfig rateLimit = new RateLimitConfig();
    private CircuitBreakerConfig circuitBreaker = new CircuitBreakerConfig();
    private CacheConfig cache = new CacheConfig();

    public enum Mode { LOCAL, REDIS }

    @Data
    public static class RedisConfig {
        private String host = "localhost";
        private int port = 6379;
        private String password;
        private int database = 0;
        private Duration connectTimeout = Duration.ofSeconds(3);
        private int poolSize = 16;
    }

    @Data
    public static class LockConfig {
        private Duration defaultLeaseTime = Duration.ofSeconds(30);
        private Duration maxWaitTime = Duration.ofSeconds(10);
        private boolean watchdogEnabled = true;  // 自动续期
    }
}

application.yml 最简配置:

# 开发环境 - 单机模式,无需Redis
distributed:
  mode: LOCAL

---
# 生产环境 - 分布式模式
spring.config.activate.on-profile: prod
distributed:
  mode: REDIS
  redis:
    host: redis-cluster.internal
    port: 6379
    password: ${REDIS_PASSWORD}
    pool-size: 32

五、DistributedModeStartupValidator 启动校验

防止配置错误导致运行时崩溃——应用启动时就做全面校验:

启动校验流程 (ApplicationReadyEvent):

  ┌────────────────────────────────────────────────────┐
  │ @EventListener(ApplicationReadyEvent.class)        │
  │ DistributedModeStartupValidator.validate()         │
  └────────────────────────┬───────────────────────────┘
                           │
                           ▼
  ┌──────────── mode == REDIS ? ───────────────────────┐
  │                                                     │
  │  YES:                                               │
  │  ├── 1. 检查Redis连接是否可达 (PING)               │
  │  ├── 2. 检查Redis版本 ≥ 6.2 (Lua脚本需要)         │
  │  ├── 3. 检查内存策略 (maxmemory-policy)            │
  │  ├── 4. 写入/读取测试Key验证读写权限               │
  │  ├── 5. 检查所有19个契约的Redis实现Bean存在         │
  │  └── 6. 检查Redisson连接池配置合理性               │
  │                                                     │
  │  NO (LOCAL):                                        │
  │  ├── 1. 警告:LOCAL模式不适合多实例部署             │
  │  ├── 2. 检查JVM堆内存是否足够 (≥512MB)             │
  │  └── 3. 注册优雅关闭钩子 (清理内存数据)            │
  │                                                     │
  │  任何检查失败:                                      │
  │  → 抛出 DistributedModeException                   │
  │  → 应用启动失败,打印明确错误信息                  │
  │  → 日志中给出修复建议                              │
  └─────────────────────────────────────────────────────┘
@Component
@RequiredArgsConstructor
public class DistributedModeStartupValidator {

    private final DistributedRuntimeProperties props;
    private final ApplicationContext context;

    @EventListener(ApplicationReadyEvent.class)
    public void validate() {
        if (props.getMode() == Mode.REDIS) {
            validateRedisConnectivity();
            validateRedisVersion();
            validateAllContractBeansPresent();
            log.info("✅ 分布式REDIS模式校验通过,所有契约就绪");
        } else {
            warnLocalModeLimitations();
            log.info("✅ 本地LOCAL模式启动,适用于单实例开发环境");
        }
    }

    private void validateAllContractBeansPresent() {
        List<Class<?>> contracts = List.of(
            DistributedLock.class,
            DistributedRateLimiter.class,
            DistributedCircuitBreaker.class,
            // ... 19个契约接口
            DistributedScheduler.class
        );
        for (Class<?> contract : contracts) {
            if (!context.getBeanNamesForType(contract).length > 0) {
                throw new DistributedModeException(
                    "REDIS模式缺少实现: " + contract.getSimpleName());
            }
        }
    }
}

六、零改动扩展机制

扩展新的分布式契约只需3步:

  Step 1: 定义接口
  ┌────────────────────────────────────────────┐
  │ public interface DistributedXxx {          │
  │     void doSomething(String key);          │
  │ }                                          │
  └────────────────────────────────────────────┘

  Step 2: 提供LOCAL实现 + REDIS实现
  ┌────────────────────────────────────────────┐
  │ @Component                                 │
  │ @ConditionalOnProperty(mode = "LOCAL")     │
  │ class LocalXxx implements DistributedXxx   │
  │                                            │
  │ @Component                                 │
  │ @ConditionalOnProperty(mode = "REDIS")     │
  │ class RedisXxx implements DistributedXxx   │
  └────────────────────────────────────────────┘

  Step 3: 注入使用(已有代码零改动)
  ┌────────────────────────────────────────────┐
  │ @Autowired DistributedXxx xxx;             │
  │ xxx.doSomething("myKey");                  │
  └────────────────────────────────────────────┘

  ✅ 不需要改任何已有代码
  ✅ 不需要改配置文件
  ✅ 不需要改启动类
  ✅ Spring自动按mode选择实现

七、面试话术

面试话术:"我们项目从一开始就设计了分布式契约层。所有状态相关操作都抽象为19个契约接口,每个接口提供Local和Redis两套实现,通过Spring的@ConditionalOnProperty在启动时自动选择。开发环境用LOCAL模式跑在内存里,零依赖就能启动;生产环境改一行配置切换到REDIS模式就获得完整的分布式能力。同时有StartupValidator在应用启动时做全面校验,确保Redis连接、版本、权限、Bean注册都正确,有问题启动就失败而不是运行时才报错。这种设计实现了业务代码与基础设施的完全解耦,新团队成员开发时完全不用关心底层是单机还是集群。"
面试话术:"具体来说,像分布式锁、限流、熔断器、去重、计数器这些组件,业务层只调用接口方法。我们做了严格的语义对齐——两种实现在功能上完全等价,LOCAL实现用ConcurrentHashMap+ReentrantLock,REDIS实现用Lua脚本保证原子性。切换模式时业务侧零感知。而且扩展新契约只需实现接口+加注解,符合开闭原则。"