定时任务模块 (Scheduler)
SayClaw 定时任务模块基于 robfig/cron/v3 构建,实现实例健康监控、日志自愈、用量同步、资源采集等全自动化运维。
最后更新:2026-03-06
架构
所有任务逻辑集中在 admin-api/cmd/server/scheduler.go,随主进程启动。
admin-api (Go)
├── scheduler.go ← cron 注册 + 所有 Job 实现
├── scheduler_api.go ← 管理 API(/job-runs /task-queue /usage-daily)
└── task_queue (MySQL) ← 事件驱动队列(apply_marketplace_item / init_instance 等)
数据库表
| 表名 | 用途 |
|---|---|
task_queue | 事件驱动异步任务队列 |
job_runs | cron 任务执行历史 |
server_metrics | 服务器资源快照(CPU/内存/磁盘) |
usage_daily | 实例每日 Token 用量汇总 |
当前任务清单
| # | 任务名 | 触发方式 | 频率 | 状态 | 说明 |
|---|---|---|---|---|---|
| 1 | queueWorker | cron | 每 5 秒 | ✅ 运行中 | 扫 task_queue 并执行异步任务 |
| 2 | health_check | cron | 每 5 分钟 | ✅ 运行中 | SSH 检查服务状态 + HTTP fallback;更新 health_status |
| 3 | auto_recovery | cron | 每 10 分钟 | ✅ 运行中 | fail_count ≥ 3 的实例自动 SSH restart |
| 4 | log_scan | cron | 每 5 分钟 | ✅ 运行中 | 扫描实例日志 + 主动端口/TG配置检测,发现错误自动修复(见下文) |
| 5 | server_metrics | cron | 每 5 分钟 | ✅ 运行中 | SSH 采集 CPU/内存/磁盘,写 server_metrics |
| 6 | token_usage_sync | cron | 每 15 分钟 | ✅ 运行中 | 从 chat_messages 同步 Token 用量到 usage_daily |
| 7 | key_validity_check | cron | 每 30 分钟 | ✅ 运行中 | 验证 LiteLLM virtual key 有效性,修复 auth-profiles.json 不一致 |
| 8 | key_balance_alert | cron | 每小时 | ⏸️ 暂停 | Key 余额 < 阈值时 TG 告警(P2) |
事件驱动任务(task_queue)
| 任务类型 | 触发时机 | 说明 |
|---|---|---|
init_instance | 用户认领实例(POST /me/instances/claim) | SSH 写 openclaw.json + BOOTSTRAP.md,重启服务 |
apply_marketplace_item | 用户安装应用市场项目 | SSH 写技能/配置文件,清 session 缓存,重启,更新 install_count |
sync_models | 管理员触发模型同步 | 将 ai_models 表的模型列表同步到实例 openclaw.json |
log_scan — 日志自动巡检 + 自愈(9 条规则)
log_scan 每 5 分钟 SSH 读取每个实例近 5 分钟的 journalctl 日志,同时主动检测端口监听状态和配置文件完整性,匹配以下错误模式并自动修复:
| # | 检测方式 | 错误特征 | 根因 | 自愈动作 |
|---|---|---|---|---|
| ① | 日志关键词 | No API key found for provider "litellm" | auth-profiles.json 损坏或 key 丢失 | 从 DB oc_api_keys 取 key → 重写文件 → stop + kill port + start |
| ② | 日志统计 | exit-code 1 出现 ≥ 3 次 | openclaw.json 配置异常 | 运行 openclaw doctor --fix → restart |
| ③ | 日志关键词 | ENOMEM / out of memory | 内存耗尽 | 标记 health_status=unhealthy(需人工介入) |
| ④ | 日志关键词 | Config invalid + Unrecognized key | openclaw.json 含不支持字段(如 controlUi.port) | python 删除 controlUi 中未知字段 → restart |
| ⑤ | 主动读配置文件 | openclaw.json 含 auth.profiles 字段 | auth.profiles 覆盖 auth-profiles.json 导致 key 找不到 | python 删除 auth.profiles 字段 → stop + kill port + start |
| ⑥ | 日志关键词 | another gateway instance is already listening | 端口被其他进程(如 browser control)占用 | kill 占用 PID → stop + start |
| ⑦ | 主动检测端口 | service=active 但端口未在 0.0.0.0 监听 | gateway 实际未绑定外网,TG Bot 无法接收消息 | kill 占用 PID → stop + start |
| ⑧ | 日志统计 | API rate limit reached ≥ 3 次 | API 调用超速率限制 | 标记 health_status=rate_limited(只告警,不自动切模型) |
| ⑨ | 主动读 openclaw.json | channels.telegram.enabled=false 或 botToken 缺失 | OpenClaw 覆写 openclaw.json 时把 TG 配置清空 | 从 DB sayclaw_portal.user_instances 取 bot token → 写回 openclaw.json → restart |
规则 ⑦ 是 TG 不回消息的主动防护:即使 systemd 状态为 active,若端口未在 0.0.0.0 监听,TG 消息无法到达,log_scan 会在 5 分钟内发现并修复。
规则 ⑨ 防止 OpenClaw 自动 config migration 覆写文件时意外清空 TG Bot 配置,确保用户绑定的 Bot 始终在线。
修复结果写入 job_runs,可在 Admin 后台「调度管理」→「Job 执行记录」查看。
key_validity_check — Key 一致性检查
每 30 分钟验证:
- LiteLLM 中 virtual key 是否有效(
GET /key/info) - 实例 auth-profiles.json 中的 key 是否与 DB
oc_api_keys一致 - 若不一致 → 重写 auth-profiles.json → restart 服务
关键流程:认领 → 自动初始化
用户 POST /me/instances/claim
↓
① INSERT user_instances
② INSERT task_queue { type: "init_instance" }
↓ (< 10s,queueWorker 拉取)
③ SSH 写 openclaw.json(dmPolicy + allowFrom)
④ SSH 写 BOOTSTRAP.md(引导 AI 完成个性化设置)
⑤ systemctl stop + fuser -k PORT + systemctl start
⑥ task_queue.status = 'done'
health_check 策略
SSH 优先,HTTP fallback:
serviceName != "" && serverID != ""
→ SSH: systemctl is-active SERVICE
→ 若 SSH 不通 → HTTP: GET ws://HOST:PORT(超时 5s)
→ SSH 通但服务 inactive → 立即标记 unhealthy(不走 HTTP)
另增 Layer 2:LiteLLM key 验证(GET /key/info?key=RAW_KEY),key 失效时 health_status = key_invalid。
已知问题与处理记录
Browser Control 端口冲突(2026-03-06)
背景:OpenClaw v2026.3.2 的 Browser Control 会在 gateway_port + 2 上绑定 127.0.0.1。若多个实例端口连续分配(如 18790、18791、18792…),相邻实例的 browser control 端口会与下一个实例的 gateway 端口冲突,导致 TG Bot 无法连接。
根治方案:实例端口间距 ≥ 10,彻底避开冲突。
当前小二(ai-jp-2)端口分配:
| 实例 | Gateway 端口 | Browser Control 端口 |
|---|---|---|
| 01 | 18789 | 18791 |
| 02 | 18810 | 18812 |
| 03 | 18820 | 18822 |
| 04 | 18830 | 18832 |
| 05 | 18840 | 18842 |
| 06 | 18850 | 18852 |
| 07 | 18860 | 18862 |
| 08 | 18870 | 18872 |
| 09 | 18880 | 18882 |
| 10 | 18798 | 18800 |
当前小三(ai-jp-3)端口分配:
| 实例 | Gateway 端口 | Browser Control 端口 |
|---|---|---|
| 01 | 18910 | 18912 |
| 02 | 18920 | 18922 |
| 03 | 18930 | 18932 |
| 04 | 18940 | 18942 |
| 05 | 18950 | 18952 |
| 06 | 18960 | 18962 |
| 07 | 18970 | 18972 |
| 08 | 18980 | 18982 |
log_scan 规则 ⑦ 会主动检测该类问题并在 5 分钟内自动修复。
OpenClaw v2026.3.2 配置变更(2026-03-06)
升级到 v2026.3.2 后,以下字段格式发生变化:
| 字段 | 旧格式 | 新格式 |
|---|---|---|
| Gateway token | gateway.token | gateway.auth.token |
| Gateway mode | 无需指定 | 必须设置 gateway.mode=local 或启动参数加 --allow-unconfigured |
小三实例启动参数(ExecStart)均已加 --allow-unconfigured 以规避 config migration 覆写问题。
远程 LiteLLM 路由(小三实例)
小三(ai-jp-3)上无本地 LiteLLM,需连接小龙(35.243.76.69:4000)。
现状:小三实例直接使用 Anthropic / OpenAI 真实 API key,不经过 LiteLLM。
| 实例 | auth provider | primary model |
|---|---|---|
| jp3-01 ~ jp3-08 | anthropic + openai | anthropic/claude-sonnet-4-6 |
原因:OpenClaw 的
litellm/provider 前缀会把模型名原样(含前缀)发给 LiteLLM,而 LiteLLM 不接受带litellm/前缀的模型名,导致Unknown model错误。LiteLLM/v1/messages(Anthropic 格式)接口也未开放,因此改用直连 API key 方案。
Admin UI
admin.sayclaw.ai → 侧边栏「调度管理」,三个 Tab:
| Tab | 内容 | 刷新频率 |
|---|---|---|
| Job 执行记录 | health_check / log_scan / token_usage_sync 等运行历史 | 30s 自动刷新 |
| 任务队列 | init_instance / apply_marketplace_item 等异步任务状态 | 15s 自动刷新 |
| Token 使用统计 | 各实例每日 Token / 请求 / 费用(usage_daily 表) | 手动刷新 |
术语统一补充(2026-03-05)
- 优先统一使用
task_queue作为后台异步任务主表命名。 - 状态机统一:
pending -> running -> done|failed,失败可重试。