三态熔断器与故障转移
上一篇拆解了 ModelSelector 的选择算法,知道了它如何从一份 YAML 配置中选出一个有序的、可用的候选列表。但选好了之后呢?候选列表交给谁执行?调用第一个模型失败了怎么办?什么时候该认定一个模型挂了不再尝试?挂了的模型什么时候重新启用?
这一篇回答这些问题。我们会深入两个核心组件:ModelHealthStore(三态熔断器)和 ModelRoutingExecutor(故障转移执行器)。前者管健康状态——谁能调、谁不能调;后者管执行逻辑——怎么调、失败了怎么切换。
三态熔断器:ModelHealthStore
1. 为什么需要熔断器
假设你的电商客服系统配了四个 Chat 模型:qwen3-max、glm-4.7、qwen-plus、qwen3-local。某天百炼平台出了故障,qwen3-max 连接超时。如果不做任何处理,每次用户提问都会先尝试调 qwen3-max,等 30 秒超时(或者直接失败),然后才切换到 glm-4.7。用户的每一个问题都要多等 30 秒——这显然不可接受。
熔断器的作用就是:连续失败达到阈值后,直接标记这个模型为不可用,后续请求跳过它,不再白等。等一段冷却时间过去,再试探性地放一个请求过去看看恢复了没有。恢复了就重新启用,没恢复就继续熔断。
这就是经典的断路器(Circuit Breaker)模式,ModelHealthStore 是它在项目中的实现。
2. 数据结构
ModelHealthStore 的核心是一个 ConcurrentHashMap,key 是模型 id,value 是这个模型的健康状态:
@Component
@RequiredArgsConstructor
public class ModelHealthStore {
private final AIModelProperties properties;
private final Map<String, ModelHealth> healthById = new ConcurrentHashMap<>();
// ...
}
每个模型的健康状态由 ModelHealth 内部类表示:
private static class ModelHealth {
private int consecutiveFailures; // 连续失败次数
private long openUntil; // 熔断截止时间戳(毫秒)
private boolean halfOpenInFlight; // 是否有探测请求在飞行中
private State state; // 当前状态
private ModelHealth() {
this.consecutiveFailures = 0;
this.openUntil = 0L;
this.halfOpenInFlight = false;
this.state = State.CLOSED;
}
}
状态枚举只有三个值:
private enum State {
CLOSED,
OPEN,
HALF_OPEN
}
新创建的 ModelHealth 默认是 CLOSED 状态,所有字段归零:
| 字段 | 初始值 | 含义 |
|---|---|---|
state | CLOSED | 健康,正常放行 |
consecutiveFailures | 0 | 连续失败次数,CLOSED 状态下每次失败 +1 |
openUntil | 0L | 熔断截止时间戳,OPEN 状态下有值 |
halfOpenInFlight | false | 是否有探测请求正在执行,HALF_OPEN 状态下使用 |
熔断行为由两个配置参数控制,来自 AIModelProperties.Selection:
failureThreshold:连续失败多少次触发熔断,默认 2openDurationMs:熔断持续时间(毫秒),默认 30000(30 秒)
也就是说,默认配置下,一个模型连续失败 2 次就熔断,30 秒后才允许探测恢复。
3. 状态转换图
三态之间的转换关系用一张状态图表示:
四条转换边,每条都有明确的触发方法和条件。接下来逐个拆解这些方法的实现。