当 AI 接口出现严重故障或超时时,如果大量请求依然不断重试,不仅会耗尽本地的线程与连接池,还可能导致上游服务彻底雪崩。本文介绍一种比简单重试更安全的容错机制:断路器模式。
为什么只用重试还不够?
在前面关于 API 限流和降级的文章中,我们提到过重试(Retry)可以解决偶发的网络波动。
但是,如果上游 AI 服务真的挂了,或者响应时间变得极长(例如每个请求都要等待 30 秒才超时):
- 如果配置了 3 次重试,每个请求就会卡住 90 秒。
- 此时如果有 100 个新请求进来,它们也会全部卡住。
- 最终,本地程序的连接池耗尽、内存飙升,整个系统跟着崩溃。
面对这种持续性故障,继续重试是没有意义的。更合理的做法是:
检测到连续失败后,直接拒绝新的请求,立刻返回错误或降级,给上游恢复的时间。 这就是"断路器模式(Circuit Breaker)"。
一、断路器模式的三种状态
断路器就像电路中的保险丝,它有三种主要状态:
- 关闭(Closed):正常状态,允许所有请求通过。
- 断开(Open):故障状态,连续失败达到阈值,保险丝熔断。此时直接拒绝所有新请求,不进行实际调用。
- 半开(Half-Open):恢复状态。经过一段时间后,允许少量请求通过,试探上游是否恢复。如果成功,回到"关闭"状态;如果再次失败,退回"断开"状态。
二、用 Python 实现一个简单的断路器
我们可以自己手写一个轻量级的断路器类来理解核心逻辑:
python
import time
class CircuitBreaker:
def __init__(self, failure_threshold: int = 5, recovery_timeout: int = 30):
self.failure_threshold = failure_threshold
self.recovery_timeout = recovery_timeout
self.failures = 0
self.last_failure_time = 0
self.state = "CLOSED"
def can_request(self) -> bool:
if self.state == "CLOSED":
return True
if self.state == "OPEN":
# 检查是否过了恢复冷却期
if time.time() - self.last_failure_time >= self.recovery_timeout:
self.state = "HALF-OPEN"
return True
return False
# HALF-OPEN 状态下只允许试探性请求
return True
def record_success(self):
self.failures = 0
self.state = "CLOSED"
def record_failure(self):
self.failures += 1
self.last_failure_time = time.time()
if self.failures >= self.failure_threshold:
self.state = "OPEN"
print(f"[断路器] 连续失败 {self.failures} 次,已熔断!")
这个类维护了故障次数和最后一次故障的时间,用于判断当前是否允许发起请求。
三、结合 OpenAI-compatible API 使用断路器
将上面的断路器结合到 API 调用中:
python
from openai import OpenAI, APIConnectionError, APITimeoutError
client = OpenAI(
api_key="your-api-key",
base_url="https://your-api-domain.com/v1",
)
# 连续失败 3 次后熔断,冷却 60 秒
breaker = CircuitBreaker(failure_threshold=3, recovery_timeout=60)
def ask_llm_with_breaker(prompt: str) -> str:
if not breaker.can_request():
return "抱歉,服务当前不可用,请稍后再试。(触发断路器快速失败)"
try:
response = client.chat.completions.create(
model="your-model-name",
messages=[{"role": "user", "content": prompt}],
timeout=10.0,
)
# 请求成功,重置断路器
breaker.record_success()
return response.choices[0].message.content
except (APIConnectionError, APITimeoutError) as e:
# 网络或超时类故障,记录失败
breaker.record_failure()
raise RuntimeError(f"请求失败: {e}") from e
except Exception as e:
# 业务错误(如参数错误),不记作系统级故障
raise RuntimeError(f"其他错误: {e}") from e
运行效果 :
当 API 连续超时 3 次后,后续在 60 秒内进来的请求,连尝试都不会尝试,直接瞬间返回提示。这就保住了本地系统的性能。
四、使用成熟的 pybreaker 库
手写断路器适合理解原理,但在生产环境中,更推荐使用成熟的开源库,比如 pybreaker。
安装
bash
pip install pybreaker
使用装饰器实现断路器
python
import pybreaker
import requests
# 连续 5 次错误后熔断,冷却 30 秒
circuit_breaker = pybreaker.CircuitBreaker(fail_max=5, reset_timeout=30)
@circuit_breaker
def call_api(payload):
# 这里可以使用 requests 或 openai SDK
response = requests.post("https://api.example.com/v1/chat/completions", json=payload, timeout=5)
response.raise_for_status()
return response.json()
def safe_call():
try:
result = call_api({"model": "test", "messages": [{"role": "user", "content": "hi"}]})
return result
except pybreaker.CircuitBreakerError:
print("断路器已熔断,直接走备用逻辑或返回友好提示")
return {"error": "Service Temporarily Unavailable"}
except Exception as e:
print(f"API 请求实际失败:{e}")
return None
pybreaker 帮我们处理了状态转换、多线程安全,甚至还可以集成到 Redis 中做分布式断路器。
五、断路器与模型降级(Fallback)结合
断路器拒绝请求后,不一定只能返回报错。我们可以把它和**备用模型(Fallback)**结合起来。
当主模型(比如 ChatGPT)触发熔断,流量自动切到备用模型(比如本地部署的 Llama 或其他 API):
python
def robust_ask(prompt: str):
try:
# 尝试走主线路(带断路器保护)
return ask_primary_llm(prompt)
except pybreaker.CircuitBreakerError:
# 熔断发生,立刻无缝切换到备用模型
print("主线路已熔断,自动降级到备用模型...")
return ask_backup_llm(prompt)
except Exception as e:
# 主线路的普通错误,也可以根据需要重试或降级
return ask_backup_llm(prompt)
这种架构可以在主服务崩溃时,立刻止损,并保证用户依然能得到服务响应。
六、哪些错误不应该触发熔断?
非常重要的一点:不是所有错误都要记入断路器的失败次数。
- 应该熔断:Timeout、502 Bad Gateway、503 Service Unavailable、Connection Refused。这些说明上游挂了。
- 不应该熔断:400 Bad Request(你传的参数错了)、401 Unauthorized(你的 Key 填错了)。
如果是你代码写的参数错误导致 400,哪怕连续报错 100 次,上游服务也是健康的,熔断只会导致业务彻底瘫痪。配置断路器时一定要指明监听哪些特定异常。
七、结语
在开发 AI 工具和自动化服务时,请求链路越长,稳定性挑战越大:
- 偶发抖动:用 重试(Retry)
- 并发过高:用 限流(Rate Limit)
- 持续故障:用 断路器(Circuit Breaker)
- 无法恢复:用 降级(Fallback)
这四种机制结合,才能构建出真正不崩溃、高可用的 API 调用架构。
如果你正在构建自己的项目,建议先从引入重试和超时控制开始,当请求量上升后再逐步引入断路器。
免责声明
本文内容仅用于技术交流与经验分享,具体实现请结合项目实际情况和并发量进行调整。