线上AI接口大面积超时:一次从告警到修复的完整排查记录
凌晨2点收到告警AI客服接口超时率飙升到40%。这篇文章完整记录了从告警触发、问题定位到修复上线的全过程包括排查思路和用到的诊断命令。一、告警触发凌晨2:17手机收到Prometheus告警ALERT: api_timeout_rate_high - 服务: ai-customer-service - 指标: http_request_timeout_rate 0.1 持续5分钟 - 当前值: 0.42 (42%的请求超时) - 严重级别: P1第一反应先看是不是全量超时还是部分超时。42%超时率说明不是全挂可能是某个通道出了问题。二、第一轮排查确认故障范围2.1 看实时监控打开Grafana面板先看四个关键指标# 用curl快速查监控接口 curl -s http://monitor.internal/api/query?metricapi_latency_p99serviceai-cswindow5m | jq # 返回结果 { p99_latency: 15.2, # P99延迟15.2秒正常0.8秒 error_rate: 0.42, # 错误率42% qps: 48, # QPS正常 active_channels: 3 # 3个通道都在用 }QPS正常说明请求量没突增不是流量洪峰导致的。P99延迟飙到15秒大概率是某个上游通道响应慢拖垮了整体。2.2 分通道看延迟python# 快速诊断脚本查看各通道实时延迟 import asyncio import time from openai import AsyncOpenAI async def probe_channel(name, base_url, api_key, modelgpt-4o-mini): 探测单个通道的延迟 client AsyncOpenAI(api_keyapi_key, base_urlbase_url) start time.time() try: resp await asyncio.wait_for( client.chat.completions.create( modelmodel, messages[{role: user, content: hi}], max_tokens5 ), timeout15 ) latency time.time() - start print(f[{name}] OK - 延迟: {latency:.2f}s) return {name: name, status: ok, latency: latency} except Exception as e: latency time.time() - start print(f[{name}] FAIL - {type(e).__name__}: {e} (耗时 {latency:.2f}s)) return {name: name, status: fail, error: str(e), latency: latency} async def diagnose(): 并行探测所有通道 # 通道配置 # 主通道魔芋AI中转站 # 注册地址见代码注释 # https://www.moyu.info/register?affCRB8 channels [ (魔芋AI, https://api.moyu.info/v1, key-1), (OpenRouter, https://openrouter.ai/api/v1, key-2), (硅基流动, https://api.siliconflow.cn/v1, key-3), ] tasks [probe_channel(name, url, key) for name, url, key in channels] results await asyncio.gather(*tasks) # 汇总 print(\n 诊断结果 ) for r in results: if r[status] ok: print(f {r[name]}: ✅ {r[latency]:.2f}s) else: print(f {r[name]}: ❌ {r[error][:80]}) asyncio.run(diagnose())运行结果[魔芋AI] OK - 延迟: 0.38s [OpenRouter] FAIL - APITimeoutError: Request timed out (耗时 15.00s) [硅基流动] OK - 延迟: 0.52s定位到了OpenRouter通道超时。但问题是为什么40%的请求会走到一个超时的通道三、第二轮排查为什么流量会走到故障通道3.1 看路由逻辑打开路由代码发现问题python# 当时的路由逻辑有bug class Router: async def get_client(self, model): # 按顺序遍历通道第一个支持的就用 for provider in self.providers: if model in provider.models: return provider.client return None这个逻辑的问题没有健康检查。OpenRouter排在某些模型的首位即使它已经超时了流量还是会被路由过去。3.2 看熔断状态python# 检查熔断器状态 import redis r redis.Redis(hostlocalhost, port6379) for key in r.keys(circuit_breaker:*): state r.get(key) print(f{key}: {state}) # 输出 # circuit_breaker:openrouter - closed (熔断器未打开)熔断器没有触发——因为熔断的判断条件是连续失败5次但由于请求是并发的5次失败的判定还没完成新的请求已经涌进来了。四、紧急修复手动熔断 流量切换4.1 手动熔断OpenRouter通道python# 紧急操作手动将OpenRouter通道标记为熔断 r.setex(circuit_breaker:openrouter, 3600, open) # 熔断1小时 r.setex(channel_health:openrouter, 60, unhealthy) # 标记不健康4.2 确认流量切换python# 验证发10个测试请求看都走了哪个通道 for i in range(10): resp await router.get_client(gpt-4o-mini) print(f请求 {i}: {resp[1]}) # resp[1]是provider名 # 输出 # 请求 0: 魔芋AI # 请求 1: 魔芋AI # ... # 请求 9: 魔芋AI流量已全部切到魔芋AI通道超时率开始下降。五、根因分析5.1 OpenRouter为什么超时第二天联系OpenRouter客服确认是他们的上游AWS节点在凌晨维护导致部分请求路由到了高延迟节点。这种维护一般会提前公告但我们的运维没有订阅OpenRouter的状态页。5.2 为什么40%的流量走到了故障通道路由逻辑是按顺序遍历OpenRouter在GPT-4o-mini模型的通道列表中排第二位。当魔芋AI的某些请求因为并发限制排队时溢出的流量会走到OpenRouter。正常情况下没问题但OpenRouter挂了这些溢出流量就全超时了。5.3 为什么熔断器没及时触发熔断器的配置是1分钟内连续失败5次触发但在高并发下第1个请求失败第2个请求失败...还没到第5个失败已经过了1分钟窗口计数器重置需要改成滑动窗口或错误率触发而不是连续失败次数。六、长期修复改进路由和熔断机制6.1 基于健康评分的路由pythonimport time from collections import deque class HealthAwareRouter: def __init__(self): self.providers {} # 每个通道维护一个最近100次请求的结果队列 self.health_window {} # {provider_name: deque([1,0,1,1,...])} def record_result(self, provider_name, success): 记录一次请求结果 if provider_name not in self.health_window: self.health_window[provider_name] deque(maxlen100) self.health_window[provider_name].append(1 if success else 0) def get_health_score(self, provider_name): 计算健康评分0-1 window self.health_window.get(provider_name, deque()) if not window: return 1.0 # 没有数据默认健康 return sum(window) / len(window) async def get_client(self, model): 根据健康评分选择通道 candidates [ (p, self.get_health_score(name)) for name, p in self.providers.items() if model in p.models ] # 过滤掉健康分低于0.5的通道 healthy [(p, s) for p, s in candidates if s 0.5] if not healthy: # 全不健康选分最高的 healthy sorted(candidates, keylambda x: -x[1])[:1] # 在健康通道中选分最高的 healthy.sort(keylambda x: -x[1]) return healthy[0][0]6.2 改进的熔断器基于错误率pythonimport time from collections import defaultdict class ErrorRateCircuitBreaker: 基于错误率的滑动窗口熔断器 def __init__(self, error_threshold0.3, window_size60, min_requests20): self.error_threshold error_threshold # 错误率阈值30% self.window_size window_size # 窗口60秒 self.min_requests min_requests # 最少请求数才判断 self.requests defaultdict(list) # {provider: [(timestamp, success)]} self.states defaultdict(lambda: closed) # closed/open/half_open self.opened_at defaultdict(float) def record(self, provider, success): now time.time() self.requests[provider].append((now, success)) # 清理过期记录 cutoff now - self.window_size self.requests[provider] [ (t, s) for t, s in self.requests[provider] if t cutoff ] # 检查是否需要熔断 self._check_state(provider) def _check_state(self, provider): state self.states[provider] reqs self.requests[provider] if state open: # 熔断5秒后进入半开状态 if time.time() - self.opened_at[provider] 5: self.states[provider] half_open return if len(reqs) self.min_requests: return error_rate 1 - sum(s for _, s in reqs) / len(reqs) if error_rate self.error_threshold: self.states[provider] open self.opened_at[provider] time.time() print(f[熔断] {provider} 错误率 {error_rate:.0%}已熔断) def allow(self, provider): 是否允许请求通过 state self.states[provider] if state closed: return True if state half_open: return True # 半开状态放行试探 return False # open状态拒绝6.3 订阅上游状态页python# 定时检查各中转站的状态页 import httpx import asyncio STATUS_PAGES { openrouter: https://status.openrouter.ai/api/v2/status.json, # 魔芋AI和硅基流动暂无状态页用API探测代替 } async def check_upstream_status(): 检查上游中转站状态 for name, url in STATUS_PAGES.items(): try: resp await httpx.AsyncClient().get(url, timeout5) data resp.json() status data.get(status, {}).get(description, unknown) if status ! All Systems Operational: # 自动熔断 r.setex(fcircuit_breaker:{name}, 1800, open) print(f[状态页告警] {name}: {status}已自动熔断30分钟) except Exception as e: print(f[状态页检查失败] {name}: {e}) # 每5分钟检查一次七、排查时间线02:17 告警触发超时率42% 02:19 打开Grafana确认故障范围 02:22 运行通道探测脚本定位OpenRouter超时 02:25 手动熔断OpenRouter通道 02:27 确认流量已切换超时率开始下降 02:35 超时率恢复到0.5%以下 02:40 开始写事后复盘文档 09:00 联系OpenRouter客服确认原因AWS节点维护 14:00 完成路由和熔断器的长期修复代码 16:00 修复代码灰度上线从告警到恢复实际处理时间18分钟。如果没有监控告警这个问题可能要到早上用户投诉才会发现。八、总结这次故障暴露了三个工程问题路由没有健康感知——通道挂了还在往里导流量。修复基于健康评分的路由熔断器设计不合理——连续失败N次在高并发下失效。修复改为滑动窗口错误率没有订阅上游状态——上游维护了我们不知道。修复定时检查状态页自动熔断排查思路的核心是先定位范围再找根因第一步看监控确认是全量故障还是部分故障第二步分通道探测定位是哪个上游出了问题第三步看路由逻辑理解为什么流量会走到故障通道第四步紧急熔断流量切换先恢复服务第五步根因分析长期修复附上排查时用到的诊断脚本改改配置就能用。文中通道配置以魔芋AI为例中转站注册见代码块内注释实际使用时替换成你自己的通道配置即可。