fix: 5xx (503) and read-timeout now try next key instead of break/fail — key1 down but key6/key5 healthy should succeed; wide 180s per-key timeout only as safety net (no per-request deadline)
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user