调用多个AI 模型时,如何实现一个简单的熔断机制
文章摘要:AI模型服务熔断机制的轻量级实现 本文介绍了一种为AI模型服务设计的轻量级熔断机制实现方案。当调用Chat、Embedding等AI模型服务时,常会遇到超时、限流等问题,熔断机制可避免持续调用故障服务。方案采用三个状态(CLOSED、OPEN、HALF_OPEN)管理模型健康度,通过记录连续失败次数、熔断时间和半开探测标记,实现自动熔断和恢复。核心逻辑包括:调用前检查状态、成功时重置健康
调用 AI 模型时,如何实现一个简单的熔断机制
适合场景:RAG 应用、智能客服、知识库问答、内容生成、Embedding 检索、Rerank 精排等需要调用 AI 模型服务的系统。
前言
在 AI 应用里,我们经常会调用不同类型的模型服务,例如:
Chat 模型:负责对话、问答、总结、生成
Embedding 模型:负责把文本转换成向量
Rerank 模型:负责对检索结果做精排
这些模型可能来自云厂商,也可能是本地部署的推理服务,比如 Ollama、vLLM、Text Embeddings Inference、Rerank Server 等。
在理想情况下,模型服务稳定可用,请求来了就调用模型,模型返回结果。但真实线上环境通常没这么顺利。
常见问题包括:
模型接口超时
供应商限流
网络连接异常
API Key 配置错误
本地模型服务正在重启
GPU 显存不足
模型接口返回异常响应
如果某个模型已经连续失败,系统还继续把请求打过去,就会带来几个问题:
用户请求响应变慢
失败请求持续堆积
下游模型服务压力更大
备用模型没有及时接管
刚恢复的模型可能被瞬间打爆
这时就需要一个简单的熔断机制。
它的核心思想很朴素:
模型正常时,继续调用;
模型连续失败时,暂时跳过;
过一段时间后,放一个请求试探它是否恢复;
如果恢复成功,重新启用;
如果仍然失败,继续熔断。
本文介绍一种轻量级实现方式,重点讲清楚逻辑,不依赖复杂框架。
技术原理
1. 熔断机制解决什么问题
假设系统里配置了两个 Rerank 模型:
主模型:远程 Rerank 模型,效果更好
备用模型:Noop Rerank,只做简单截断
正常情况下,请求优先调用主模型:
用户问题 + 候选文档
-> 主 Rerank 模型
-> 返回相关性排序后的结果
如果主模型接口异常,系统可以切换到备用模型:
主模型调用失败
-> 记录失败
-> 尝试备用模型
-> 返回降级结果
如果主模型连续失败多次,就不要每次都继续尝试它,而是让它冷却一段时间:
主模型连续失败
-> 进入熔断状态
-> 一段时间内跳过主模型
-> 直接尝试备用模型
这就是熔断机制的价值:减少无效请求,保护下游服务,并让系统具备更稳定的降级能力。
2. 熔断器的三个状态
一个简单熔断器通常只需要三个状态:
CLOSED 正常状态,可以调用模型
OPEN 熔断状态,暂时不调用模型
HALF_OPEN 半开状态,允许一个请求试探模型是否恢复
状态流转如下:
CLOSED
-> 连续失败达到阈值
-> OPEN
OPEN
-> 熔断时间结束
-> HALF_OPEN
HALF_OPEN
-> 探测成功:恢复 CLOSED
-> 探测失败:重新 OPEN
可以把它理解成一个比较谨慎的保护策略:
CLOSED:我认为模型是好的,正常调用。
OPEN:我认为模型短时间内不可用,先别调用。
HALF_OPEN:我不确定模型是否恢复了,先放一个请求试试。
3. 需要保存哪些健康状态
对每个模型,只需要维护少量状态:
class HealthState {
int consecutiveFailures;
long openUntil;
boolean halfOpenInFlight;
State state;
}
字段含义如下:
consecutiveFailures 连续失败次数
openUntil 熔断结束时间
halfOpenInFlight 半开状态下是否已有探测请求正在执行
state 当前状态
这些状态可以按模型 ID 保存:
modelId -> HealthState
例如:
chat-model -> CLOSED
embedding-model -> CLOSED
rerank-model -> OPEN
这样设计有一个好处:每个模型的健康状态互不影响。
Chat 模型失败,不影响 Embedding 模型;Rerank 模型失败,也不影响 Chat 模型。
4. 调用模型前:判断是否允许调用
每次调用模型前,先判断一次:
这个模型当前还能不能调用?
核心逻辑如下:
如果是 CLOSED:
允许调用
如果是 OPEN:
如果还没到熔断结束时间:
不允许调用
如果已经到了熔断结束时间:
切换到 HALF_OPEN
允许一个请求试探
如果是 HALF_OPEN:
如果已经有探测请求在执行:
不允许调用
否则:
允许一个探测请求
简化代码:
boolean allowCall(String modelId) {
HealthState h = getState(modelId);
if (h.state == State.CLOSED) {
return true;
}
if (h.state == State.OPEN) {
if (System.currentTimeMillis() < h.openUntil) {
return false;
}
h.state = State.HALF_OPEN;
h.halfOpenInFlight = true;
return true;
}
if (h.state == State.HALF_OPEN) {
if (h.halfOpenInFlight) {
return false;
}
h.halfOpenInFlight = true;
return true;
}
return false;
}
这里的 halfOpenInFlight 很关键。
它的作用是控制半开探测数量。模型刚从熔断期出来时,不应该让大量请求同时打过去,而是先放一个请求试探。
如果这个请求成功,说明模型恢复了;如果失败,说明模型还不可用。
5. 调用成功后:恢复健康状态
如果模型调用成功,就认为它当前是健康的。
这时可以执行 markSuccess:
void markSuccess(String modelId) {
HealthState h = getState(modelId);
h.state = State.CLOSED;
h.consecutiveFailures = 0;
h.openUntil = 0;
h.halfOpenInFlight = false;
}
成功后的处理很直接:
状态恢复为 CLOSED
连续失败次数清零
熔断结束时间清零
半开探测标记清除
也就是说,只要一次调用成功,就认为模型可以重新进入正常服务状态。
6. 调用失败后:累计失败并触发熔断
如果模型调用失败,就记录一次失败。
当连续失败次数达到阈值时,进入熔断状态:
void markFailure(String modelId) {
HealthState h = getState(modelId);
long now = System.currentTimeMillis();
if (h.state == State.HALF_OPEN) {
h.state = State.OPEN;
h.openUntil = now + openDurationMs;
h.consecutiveFailures = 0;
h.halfOpenInFlight = false;
return;
}
h.consecutiveFailures++;
if (h.consecutiveFailures >= failureThreshold) {
h.state = State.OPEN;
h.openUntil = now + openDurationMs;
h.consecutiveFailures = 0;
}
}
比如配置为:
failureThreshold = 2
openDurationMs = 30000
含义是:
连续失败 2 次后熔断
熔断持续 30 秒
半开状态下如果调用失败,不需要继续累计失败次数,而是直接重新熔断。
因为半开请求本来就是恢复性探测。探测失败,就说明模型还没恢复。
7. 和模型路由结合
熔断机制通常不是单独使用,而是和模型路由、故障转移一起使用。
假设有一组候选模型:
候选 1:主模型
候选 2:备用模型
候选 3:本地兜底模型
调用流程可以设计成:
按优先级遍历候选模型
-> 检查当前模型是否允许调用
-> 如果正在熔断,跳过
-> 如果允许调用,尝试请求模型
-> 成功则返回结果
-> 失败则记录失败,并尝试下一个模型
简化代码:
for (ModelTarget target : targets) {
if (!healthStore.allowCall(target.id())) {
continue;
}
try {
Result result = callModel(target);
healthStore.markSuccess(target.id());
return result;
} catch (Exception ex) {
healthStore.markFailure(target.id());
}
}
throw new RuntimeException("All model candidates failed");
这样,当主模型不可用时,请求不会一直卡在主模型上,而是会继续尝试后面的候选模型。
这就是一个简单的故障转移过程。
8. 这是被动健康监测
需要注意的是,这种方案属于被动健康监测。
它不会主动定时请求模型接口,也不会后台 ping 模型服务。
它只根据真实业务调用结果更新健康状态:
真实调用成功 -> 认为模型健康
真实调用失败 -> 记录失败
连续失败过多 -> 进入熔断
熔断时间结束 -> 用下一次真实请求做半开探测
这种方式的优点是简单、轻量,不需要额外线程,也不依赖专门的健康检查接口。
缺点也很明确:如果模型已经恢复,但一直没有新请求进来,系统不会主动发现它恢复。只有下一次真实请求到来时,才会触发半开探测。
9. 单机和分布式场景
如果应用只有一个实例,健康状态可以直接保存在内存里。
例如:
Map<String, HealthState> healthMap = new ConcurrentHashMap<>();
这已经能满足很多简单场景。
但如果应用是多实例部署,并且多个实例调用的是同一个模型服务,那么只保存在本地内存里就不够了。
例如:
应用实例 A 发现模型失败,并进入熔断
应用实例 B 不知道,仍然继续调用
应用实例 C 也不知道,仍然继续调用
这种情况下,熔断状态是不一致的。
如果希望多个应用实例共享同一个模型健康状态,可以把状态放到 Redis 里:
model-health:rerank-model -> OPEN
model-health:chat-model -> CLOSED
分布式场景还需要特别注意半开探测。
如果多个实例同时发现熔断时间到了,可能会一起把请求打到刚恢复的模型上。更稳妥的做法是用 Redis 锁控制:
同一时间,只允许一个实例执行半开探测
可以用类似下面的方式实现:
SET model-health:rerank-model:probe-lock 1 NX PX 30000
谁抢到锁,谁执行探测;其他实例继续跳过。
一个完整调用过程示例
以 Rerank 模型为例:
1. 请求进入系统,需要对检索结果做精排
2. 系统优先选择主 Rerank 模型
3. 调用前执行 allowCall(modelId)
4. 如果模型处于 CLOSED,允许调用
5. 模型调用失败,执行 markFailure(modelId)
6. 失败次数达到阈值,模型进入 OPEN
7. 后续请求在 30 秒内跳过该模型
8. 系统自动尝试备用模型
9. 30 秒后,下一次请求触发 HALF_OPEN 探测
10. 探测成功,模型恢复 CLOSED
11. 探测失败,模型重新进入 OPEN
这个过程不复杂,但能让模型调用链路稳定很多。
总结
一个简单的 AI 模型熔断机制,可以总结为几句话:
调用前判断是否允许访问;
调用成功后恢复健康;
调用失败后累计失败;
连续失败达到阈值后熔断;
熔断时间结束后允许一个请求探测;
探测成功则恢复,失败则继续熔断。
它的优势是:
实现简单
不依赖复杂框架
适合模型路由和故障转移
可以快速提升系统稳定性
它的边界是:
本地内存状态不适合多实例共享
被动监测无法主动发现模型恢复
半开探测需要控制并发
失败原因需要谨慎区分,避免误熔断
对于大多数 AI 应用来说,可以先实现一个轻量级的内存熔断器。
当系统进入多实例部署,或者多个服务共享同一个本地模型服务时,再把健康状态迁移到 Redis 这类共享存储中,是比较自然的演进方向。
总结
AI 应用在调用 Chat、Embedding、Rerank 等模型服务时,经常会遇到接口超时、供应商限流、网络异常、本地模型重启等问题。本文介绍了一种简单的熔断机制:通过 CLOSED、OPEN、HALF_OPEN 三种状态管理模型健康,在真实调用成功或失败后被动更新状态,并结合模型路由实现失败后的自动切换,从而提升 AI 模型调用链路的稳定性。
更多推荐




所有评论(0)