From 39aa904184c316c697fa208b090752df7ca35094 Mon Sep 17 00:00:00 2001 From: cfdaily Date: Tue, 2 Jun 2026 22:03:37 +0800 Subject: [PATCH] auto-sync: 2026-06-02 22:03:37 --- docs/design/gateway-watchdog.md | 227 ++++++++++++++++++-------------- 1 file changed, 128 insertions(+), 99 deletions(-) diff --git a/docs/design/gateway-watchdog.md b/docs/design/gateway-watchdog.md index 959fd17..9699c28 100644 --- a/docs/design/gateway-watchdog.md +++ b/docs/design/gateway-watchdog.md @@ -1,144 +1,161 @@ # Gateway Watchdog 设计文档 -**版本**: 1.0 +**版本**: 2.0 **作者**: 庞统(副军师)🐦 -**日期**: 2026-05-28 +**日期**: 2026-06-02 **状态**: 已实现 --- ## 1. 问题背景 -zhipu GLM-5.1 在高峰期返回 **429 限流**("该模型当前访问量过大,请您稍后再试",errorCode=1305),导致: +### 1.1 历史问题(v1.0 覆盖) -1. Gateway 调用失败,`stopReason=error` -2. Session 进入 stalled 状态 -3. stalled 后无法自动恢复,必须手动重启 Gateway -4. 所有 Agent 假死,直到人工介入 +zhipu GLM-5.1 在高峰期返回 **429 限流**,导致 Gateway 假死。 -### 历史记录 +### 1.2 新增问题(v2.0 新增) -- 2026-05-26 09:00 CST — 429 导致假死 -- 2026-05-28 ~10:00 CST — 429 导致假死,持续约 1 小时 -- 累计 9 次 session.stalled 事件 +线上观察发现两种新的致命信号,Gateway 自身无法恢复,必须重启: -## 2. 双层防护 +| 信号 | 日志关键字 | 现象 | +|------|-----------|------| +| **所有 provider 挂了** | `lane task error` + `FailoverError` | 主模型 + fallback 全部失败,所有 Agent 无法工作 | +| **Session 卡死** | `stalled session` + `recovery=none` | Gateway diagnostic 检测到 session 卡死但承认自己恢复不了 | -### 2.1 第一层:跨 Provider Fallback(立即生效) +### 1.3 为什么 v1.0 检测不到 -**配置**: `~/.openclaw/openclaw.json` +v1.0 扫描的是 **session jsonl**(`~/.openclaw/agents/*/sessions/*.jsonl`),但这些关键字实际出现在 **Gateway 进程日志**(`/tmp/openclaw/openclaw-{日期}.log`)中。两类日志的数据格式完全不同。 +## 2. 检测规则(v2.0) + +### 2.1 数据源 + +**Gateway 进程日志**:`/tmp/openclaw/openclaw-$(date +%Y-%m-%d).log` + +- JSON lines 格式,每行一条记录 +- 关键字在 `"message"` 字段中(也在 `"1"` 字段中有副本) +- 每天自动轮转,watchdog 只读当天文件 + +### 2.2 三条规则 + +| 规则 | 匹配条件 | 阈值 | 含义 | 严重度 | +|------|---------|------|------|--------| +| **R1** | `message` 含 `lane task error` **且** 含 `FailoverError` | ≥2 次/120s | 所有 provider 都挂了,不重启没救 | 🔴 致命 | +| **R2** | `message` 含 `stalled session` **且** 含 `recovery=none` | ≥3 次/120s | Gateway 承认自己恢复不了 | 🟠 严重 | +| **R3** | `message` 含 `rate_limit` **或** 含 `"429"` | ≥2 次/120s | API 限流 | 🟡 警告 | + +### 2.3 匹配示例 + +**R1 匹配日志行**: ```json -{ - "model": { - "primary": "zhipu/glm-5.1", - "fallbacks": ["deepseek/deepseek-v4-pro"] - } -} +{"_meta":{"logLevelName":"ERROR"},"message":"lane task error: lane=main durationMs=93772 error=\"FailoverError: ⚠️ API rate limit reached. Please try again later.\""} ``` -- zhipu 429 时自动切 deepseek(不同 provider,不会一起死) -- deepseek-v4-pro 也是免费的,支持 1M 上下文 -- Gateway 原生支持,无需额外代码 - -### 2.2 第二层:Watchdog 自动重启(兜底) - -即使 fallback 也失败(所有 provider 同时挂),watchdog 检测到连续 429 后自动重启 Gateway。 - -## 3. Watchdog 设计 - -### 3.1 检测原理 - -**数据源**: Gateway 的 session jsonl 日志 - -``` -~/.openclaw/agents/{agent-id}/sessions/*.jsonl -``` - -**429 精确特征**: +**R2 匹配日志行**: ```json -{ - "message": { - "stopReason": "error", - "errorCode": "1305", - "errorMessage": "429 该模型当前访问量过大,请您稍后再试" - } -} +{"_meta":{"logLevelName":"WARN"},"message":"stalled session: sessionKey=agent:main:main state=processing age=266s recovery=none"} ``` -**判定条件**(全部满足才算 429): -1. `stopReason === "error"` -2. `errorMessage` 包含 `"429"` 或 `errorCode` 为 `"1305"` +**R3 匹配日志行**: +```json +{"_meta":{"logLevelName":"WARN"},"message":"lane task error: ... error=\"FailoverError: ...\"","providerRuntimeFailureKind":"rate_limit"} +``` -**不会误判**: -- 正常 stop(`stop=stop`、`stop=toolUse`)不算 -- 其他 error(如 context overflow 的 errorCode 不同)不算 -- `stopReason` 不是 `"error"` 的不算 - -### 3.2 检测流程 +### 2.4 判定逻辑 ``` 每分钟执行 - │ - ├─ Gateway health check - │ ├─ 失败 → 直接重启(可能 Gateway 进程已死) - │ └─ 成功 → 继续 - │ - ├─ 遍历所有 agent session jsonl - │ ├─ 跳过 trajectory 文件 - │ ├─ 跳过 120 秒内未修改的文件(性能优化) - │ ├─ 跳过 <100 字节的文件 - │ └─ 统计 CHECK_WINDOW(120s) 内的 429 错误数 - │ - ├─ 判断 - │ ├─ 有新 429 → consecutive++ - │ └─ 无新 429 → consecutive=0(重置) - │ - └─ consecutive >= THRESHOLD(3) → 重启 Gateway + 重置计数 + │ + ├─ 1. Gateway health check + │ └─ 失败 → 直接重启(可能 Gateway 进程已死) + │ + ├─ 2. 读当天 Gateway 日志,python3 过滤最近 120 秒的行 + │ + ├─ 3. 三条规则分别 grep 计数 + │ + ├─ 4. 任一规则命中阈值 + │ ├─ 在冷却期内 → 只 log 不重启 + │ └─ 不在冷却期 → 重启 Gateway + 记录原因 + │ + └─ 5. 都没命中 → 正常 ``` -### 3.3 参数 +## 3. 防重启风暴 + +- **冷却期**:重启后 5 分钟内再次检测到问题只 log 不重启 +- **状态文件**:`/tmp/gateway-watchdog-state`(JSON) + ```json + {"last_restart_time":"2026-06-02T21:00:00+08:00","last_restart_reason":"R1","cooldown_until":1717333800} + ``` + +## 4. 重启原因记录 + +每次重启自动追加到 `/tmp/gateway-watchdog-restarts.log`(永久文件,用于统计分析): + +```json +{"time":"2026-06-02T21:00:00+0800","reason":"R1","detail":"FailoverError x3","counts":{"r1":3,"r2":0,"r3":1}} +``` + +字段说明: +- `time`:重启时间(ISO 格式) +- `reason`:触发规则(R1/R2/R3/health_fail) +- `detail`:匹配到的关键字摘要 +- `counts`:所有三条规则的实际命中次数(无论哪条触发都记录全量) + +## 5. 参数 | 参数 | 默认值 | 说明 | |------|--------|------| | CHECK_WINDOW | 120s | 检查最近多少秒的日志 | -| THRESHOLD | 3 | 连续检测到多少次 429 才重启 | +| R1_THRESHOLD | 2 | FailoverError 触发阈值 | +| R2_THRESHOLD | 3 | stalled recovery=none 触发阈值 | +| R3_THRESHOLD | 2 | rate_limit/429 触发阈值 | +| COOLDOWN | 300s | 重启后冷却期 | | 检测频率 | 60s | crontab 每分钟执行 | -### 3.4 状态文件 +## 6. 文件位置 -`/tmp/gateway-watchdog-429-count` — 记录连续 429 检测次数,重启后重置为 0。 +| 文件 | 路径 | 说明 | +|------|------|------| +| 脚本 | `scripts/gateway-watchdog.sh` | watchdog 主脚本 | +| 本文档 | `docs/design/gateway-watchdog.md` | 设计文档 | +| 运行日志 | `/tmp/gateway-watchdog.log` | crontab 重定向输出 | +| 状态文件 | `/tmp/gateway-watchdog-state` | 冷却期控制 | +| 重启记录 | `/tmp/gateway-watchdog-restarts.log` | 重启原因(永久追加) | +| 锁文件 | `/tmp/gateway-watchdog.lock` | 防并发 | -### 3.5 日志 - -`/tmp/gateway-watchdog.log` — 每次执行的检测结果。 - -## 4. 文件位置 - -| 文件 | 路径 | -|------|------| -| 脚本 | `scripts/gateway-watchdog.sh` | -| 本文档 | `docs/design/gateway-watchdog.md` | -| 日志输出 | `/tmp/gateway-watchdog.log` | -| 状态文件 | `/tmp/gateway-watchdog-429-count` | - -## 5. 部署 +## 7. 部署 ```bash # crontab 每分钟执行 (crontab -l 2>/dev/null | grep -v "gateway-watchdog"; \ - echo "* * * * * /path/to/sanguo_moziplus_v2/scripts/gateway-watchdog.sh >> /tmp/gateway-watchdog.log 2>&1") \ + echo "* * * * * /Users/chufeng/.openclaw/sanguo_projects/sanguo_moziplus_v2/scripts/gateway-watchdog.sh >> /tmp/gateway-watchdog.log 2>&1") \ | crontab - ``` -## 6. 运维 +## 8. 运维 ```bash -# 查看日志 -tail -20 /tmp/gateway-watchdog.log +# 查看运行日志 +tail -f /tmp/gateway-watchdog.log -# 查看当前 429 计数 -cat /tmp/gateway-watchdog-429-count +# 查看状态(冷却期等) +cat /tmp/gateway-watchdog-state + +# 查看重启历史 +cat /tmp/gateway-watchdog-restarts.log | python3 -m json.tool + +# 统计重启原因 +grep "reason" /tmp/gateway-watchdog-restarts.log | python3 -c " +import json, sys +from collections import Counter +reasons = Counter() +for line in sys.stdin: + reasons[json.loads(line)['reason']] += 1 +print('重启统计:') +for k,v in reasons.most_common(): + print(f' {k}: {v}次') +" # 手动测试 bash scripts/gateway-watchdog.sh @@ -147,8 +164,20 @@ bash scripts/gateway-watchdog.sh crontab -l | grep -v "gateway-watchdog" | crontab - ``` -## 7. 已知局限 +## 9. 与 v1.0 的差异 -1. **事后检测** — 检测的是已发生的 429,不是预防 -2. **重启治标** — 重启 Gateway 恢复 stalled session,但不解决 zhipu 限流根因 -3. **无通知** — 重启后没有主动通知用户(可后续加飞书/邮件通知) +| 维度 | v1.0 | v2.0 | +|------|------|------| +| 数据源 | session jsonl 文件 | Gateway 进程日志 | +| 检测规则 | 仅 429(1 条) | 3 条(FailoverError / stalled / rate_limit) | +| 重启风暴 | 无防护 | 5 分钟冷却期 | +| 原因记录 | 无 | JSON 文件永久追加 | +| 统计能力 | 无 | 结构化数据可分析 | +| 配套脚本 | 独立(gateway_monitor.py 不动) | 不冲突 | + +## 10. 已知局限 + +1. **事后检测** — 检测的是已发生的错误,不是预防 +2. **只读当天日志** — 跨天 0 点时日志轮转,前一分钟的日志在新文件里但文件名不同(暂不处理,影响极小) +3. **无通知** — 重启后没有主动推送通知(可后续接入飞书/邮件) +4. **单机** — 只能在 Gateway 所在机器上运行