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个契约接口,覆盖分布式系统的所有核心关注点:
| # | 契约接口 | 职责 | Redis Key 模式 |
|---|---|---|---|
| 1 | DistributedLock | 分布式互斥锁 | lock:{resource} |
| 2 | DistributedRateLimiter | 分布式限流 | ratelimit:{scope}:{key} |
| 3 | DistributedCircuitBreaker | 分布式熔断器 | cb:{name}:state |
| 4 | DistributedCounter | 原子计数器 | counter:{name} |
| 5 | DistributedCache | 分布式缓存 | cache:{namespace}:{key} |
| 6 | DistributedQueue | 分布式队列 | queue:{name} |
| 7 | DistributedPubSub | 发布/订阅 | channel:{topic} |
| 8 | DistributedIdGenerator | 全局唯一ID生成 | idgen:{biz} |
| 9 | DistributedSessionStore | 会话存储 | session:{channelType}:{userId} |
| 10 | DistributedDedupeStore | 消息去重 | dedup:{channel}:{msgId} |
| 11 | DistributedLeaderElection | 主节点选举 | leader:{group} |
| 12 | DistributedHealthRegistry | 健康状态注册 | health:{nodeId} |
| 13 | DistributedTaskStore | 任务状态持久化 | task:{taskId}:state |
| 14 | DistributedCheckpointStore | 断点续传检查点 | checkpoint:{taskId}:{step} |
| 15 | DistributedMetricsCollector | 指标收集/聚合 | metrics:{name}:{window} |
| 16 | DistributedConfigStore | 动态配置中心 | config:{namespace}:{key} |
| 17 | DistributedSemaphore | 分布式信号量 | sem:{resource} |
| 18 | DistributedBloomFilter | 布隆过滤器 | bloom:{name} |
| 19 | DistributedScheduler | 分布式定时调度 | scheduler:{job}:lock |
双模式实现架构:
┌─────────────────────────────────────────────────────────┐
│ 业务层 (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 │
└──────────────────────────────────────────────┘
@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();
}
}
}
@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();
}
}
}
@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
防止配置错误导致运行时崩溃——应用启动时就做全面校验:
启动校验流程 (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选择实现