From a7a42448d426b009dc060fd4dfede194a72f9b38 Mon Sep 17 00:00:00 2001 From: hmo Date: Sat, 15 Aug 2026 20:10:58 +0800 Subject: [PATCH] =?UTF-8?q?fix:=205xx=20(503)=20and=20read-timeout=20now?= =?UTF-8?q?=20try=20next=20key=20instead=20of=20break/fail=20=E2=80=94=20k?= =?UTF-8?q?ey1=20down=20but=20key6/key5=20healthy=20should=20succeed;=20wi?= =?UTF-8?q?de=20180s=20per-key=20timeout=20only=20as=20safety=20net=20(no?= =?UTF-8?q?=20per-request=20deadline)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gateway/scripts/ocg_router.py | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/gateway/scripts/ocg_router.py b/gateway/scripts/ocg_router.py index 0509cf6..48b1c94 100644 --- a/gateway/scripts/ocg_router.py +++ b/gateway/scripts/ocg_router.py @@ -48,11 +48,9 @@ UPSTREAM_COOLDOWN_SEC = 30 # 上游模型级故障(Router.Unavailable)短 USAGE_REFRESH_INTERVAL = 120 # 2 分钟 # 最大重试次数(所有 key 耗尽) MAX_RETRIES = 6 -# 单次上游请求超时(秒):翻译等小请求正常 2-5s,60s 绰绰有余; -# 从 180s 下调,避免"单次 180s × 多 key"叠加成长时间挂死(2026-08-15 事故) -UPSTREAM_REQUEST_TIMEOUT = 60 -# 整个请求(含所有 key 重试)的总截止时间:3 key × 60s = 180s,保证 3 分钟内一定结束 -REQUEST_DEADLINE_SEC = 180 +# 单次上游请求保底超时(秒):上游处理大请求(1MB+)可能要 60-90s+, +# 故设宽超时兜底(防 TCP 挂死无限等),正常靠错误分类快速换 key,不依赖此超时 +UPSTREAM_REQUEST_TIMEOUT = 180 # ── 日志 ──────────────────────────────────────────────────── LOG_DIR = _GATEWAY_DIR / "logs" @@ -548,12 +546,6 @@ class RouterHandler(BaseHTTPRequestHandler): client_error = False # True=客户端请求错误(400/ModelError),应返回 4xx 而非 502 for attempt, k in enumerate(ranked_keys): - # 总超时保护:整个请求(含所有 key 重试)最迟在 REQUEST_DEADLINE_SEC 内结束, - # 防止"单次 60s × 多 key"叠加成 15 分钟挂死(2026-08-15 事故根因) - if time.time() - start > REQUEST_DEADLINE_SEC: - last_error = f"overall request deadline ({REQUEST_DEADLINE_SEC}s) exceeded after {attempt} key attempts" - log.warning("⚠ %s overall deadline exceeded after %d attempts", model_name, attempt) - break kid = k["key_id"] api_key = k.get("api_key", "") if not api_key: @@ -579,11 +571,12 @@ class RouterHandler(BaseHTTPRequestHandler): else: last_error = result.get("error", "unknown") log.warning("⚠ %s via %s FAILED: %s", model_name, kid, last_error) - # 错误分类:只有真正的 key 级错误才冷却 key - # - 400 客户端错误:不重试、不冷却(换 key 无意义),直接返回给调用方 - # - 401/403 认证失败:key 级故障 → 冷却 300s(但 ModelError=客户端传了不存在的模型,不冷却) + # 错误分类:只有 key 本身的问题才冷却;上游瞬时问题换 key 重试 + # - 400/ModelError:客户端错误 → 直接返回,换 key 无意义 + # - 401/403 认证失败:key 级故障 → 冷却 300s # - 402/429 配额/限流:key 级 → 冷却 60s - # - 5xx/Router.Unavailable:上游服务问题 → 不冷却 key(误杀元凶),立即失败 + # - 5xx(含 503):上游服务瞬时问题 → 不冷却,换下一个 key 重试 + # - read timeout:单 key 挂起 → 不冷却,换下一个 key(保底超时兜底) status = result.get("status", 0) if status == -1: # 客户端已断开:直接终止,不重试不冷却 @@ -608,15 +601,15 @@ class RouterHandler(BaseHTTPRequestHandler): elif status in (402, 429): mark_key_failed(kid, last_error, cooldown=60) elif status >= 500: - # 上游服务端错误(Internal server error / Router.Unavailable): - # 不杀 key。立即失败返回,避免"全部 key 被误杀进冷却"的雪崩。 - log.warning("upstream %d (service-level) via %s — NOT cooling key, failing request", status, kid) - success = False - break + # 上游服务端错误(503/500/Internal/Router.Unavailable): + # 只是当前 key 的上游实例可能有问题,换下一个 key 重试(不冷却)。 + # 之前直接 break 导致"key1 挂了但 key6/key5 正常"时浪费了可用 key。 + log.warning("upstream %d via %s (service-level) — trying next key", status, kid) + # status == 0(URLError/read timeout 等)→ 继续循环换下一个 key,不冷却 except Exception as e: last_error = str(e) log.warning("⚠ %s via %s exception: %s", model_name, kid, e) - mark_key_failed(kid, last_error) + # 异常(如 read timeout / URLError):不冷却,换下一个 key 重试 if not success: elapsed = time.time() - start