diff --git a/docs/architecture/ADR-006-sidecar-v2-architecture.md b/docs/architecture/ADR-006-sidecar-v2-architecture.md new file mode 100644 index 0000000..5303f83 --- /dev/null +++ b/docs/architecture/ADR-006-sidecar-v2-architecture.md @@ -0,0 +1,1242 @@ +# ADR-006: Sidecar V2 — Provider 池管理与负载均衡架构设计 + +| 属性 | 值 | +|------|-----| +| 编号 | ADR-006 | +| 日期 | 2026-06-25 | +| 版本 | v2.0(评审修订版) | +| 状态 | 提议中 | +| 作者 | 梁思筑 (architect) | +| 关联 | BIZ-48 (父需求), BIZ-51 (本任务) | +| 评审 | 陆怀瑾(COO), 沈路明(PM), 严维序(Ops), 徐聪(Dev) | +| 类型 | 系统架构设计 | + +--- + +## 1. 背景与目标 + +### 1.1 现状分析(Sidecar V1) + +Sidecar V1 是一个单 Provider(NVIDIA)的限流代理,核心架构: + +``` +请求 → 优先级队列 → 令牌桶限流(40 RPM) → httpx → NVIDIA API +``` + +**V1 局限性**: +- 单一 API Key,无多 Provider 管理能力 +- 无 429 自动冷却机制 +- 配置依赖 OpenClaw 环境变量 / YAML,无 WebUI 管理 +- 无 Provider 级负载均衡 +- 无成本/用量统计 + +### 1.2 V2 目标 + +将 Sidecar 从"单 Provider 透明代理"升级为"多 Provider 池化网关",实现: + +1. **Provider 池体系**:主池 + Fallback 池 + 冷却池 +2. **多 Provider 负载均衡**:每 Provider 独立 RPM 流控 +3. **429 自动冷却**:检测 429 → 进入冷却池 → 指数退避恢复 +4. **完整 WebUI**:配置管理、Dashboard 统计、健康监控 +5. **用量统计**:Token 用量、调用次数、费用核算 +6. **V1 兼容**:升级不破坏现有部署 + +--- + +## 2. 架构设计 + +### 2.1 系统拓扑图 + +```mermaid +graph TB + subgraph "OpenClaw Gateway" + OC[OpenClaw 调度器] + OC_FB[OpenClaw Fallback
传统配置链路] + end + + subgraph "Sidecar V2 (systemd / Docker)" + direction TB + + subgraph "入口层" + GW["API Gateway :9190
FastAPI + 路由匹配"] + end + + subgraph "核心调度层" + LB["负载均衡器
Weighted RR 5-10s刷新"] + QM["队列管理器
FIFO + 优先级
容量500 + 溢出策略"] + end + + subgraph "Provider 池层" + direction LR + MP["主池 Main Pool"] + FP["Fallback 池"] + CP["冷却池
Cooldown Pool"] + end + + subgraph "流控层" + RL["Rate Limiter
Per-Provider Token Bucket"] + CD["Cooldown Detector
429检测+指数退避
+紧急通道10%RPM"] + end + + subgraph "存储与统计层" + MT["Metrics :9191
Prometheus"] + ST["统计引擎
Token/费用/调用量"] + DB[("SQLite WAL
sidecar_v2.db
+ cron备份")] + end + + subgraph "WebUI 层 :9190" + UI["Dashboard
SSE 实时推送"] + AP["Admin API
Provider CRUD
Bearer Token 鉴权"] + end + end + + OC -->|"X-Sidecar-Pool: main"| GW + GW --> LB + LB --> QM + QM --> RL + RL --> MP + RL --> FP + MP -.->|"429 触发冷却"| CP + MP -->|"全部冷却"| FP + FP -->|"全部冷却"| OC_FB + CP -.->|"冷却结束恢复"| MP + RL --> CD + CD -.->|"紧急通道 10% RPM"| MP + LB --> MT + MT --> ST + ST --> DB + DB --> UI + AP --> DB +``` + +### 2.2 数据流设计 + +```mermaid +sequenceDiagram + participant OC as OpenClaw + participant GW as API Gateway + participant LB as 负载均衡器 + participant QM as 队列管理器 + participant RL as Rate Limiter + participant P as Provider + participant CD as Cooldown Detector + participant ST as 统计引擎 + + OC->>GW: POST /v1/chat/completions + GW->>LB: 路由到目标池 + + LB->>LB: Weighted RR (5-10s刷新) + 选出 Provider A + weight=(max_rpm-current_rpm)/max_rpm + + LB->>RL: BEGIN IMMEDIATE 事务 + 检查 Provider A RPM + 预占 + + alt RPM 不足 + RL->>QM: 入队等待(超时30s) + QM-->>RL: 令牌可用 + end + + RL-->>LB: 允许转发 + + LB->>P: 转发请求 + P-->>LB: 响应 + + alt 200 OK + LB->>ST: INSERT...ON CONFLICT + 记录 usage_logs + LB-->>GW: 正常响应 + + else 429 Too Many Requests + LB->>CD: 上报429 + CD->>P: Provider 移入冷却池 + cooldown_until=now+30s×2^n + + LB->>LB: 重新选择 Provider B + + alt Provider B 正常 + LB->>P: 转发到 Provider B + P-->>LB: 200 OK + end + + alt 主池全部冷却 + LB->>LB: 降级 Fallback 池 + Note over LB: 检查即将恢复的Provider + 剩余<10s→等待 + + alt Fallback 可用 + LB->>P: 转发 Fallback Provider + P-->>LB: 200 OK (+降级标记) + else Fallback 也全冷却 + LB->>P: 紧急通道: 1 Provider 10% RPM + alt 紧急通道成功 + P-->>LB: 200 OK + else + LB-->>OC: 503 Service Unavailable + OC->>OC: OpenClaw 自身 fallback + end + end + end + end +``` + +### 2.3 部署架构 + +```mermaid +graph TB + subgraph "Ubuntu Server (192.168.1.99)" + subgraph "systemd Services" + SVC["sidecar-v2.service
MemoryMax=512M
LimitNOFILE=65536
ProtectSystem=strict"] + end + + subgraph "端口规划" + P9190[":9190 API + WebUI + Admin"] + P9191[":9191 Prometheus Metrics"] + end + + subgraph "存储" + DBFILE["/opt/sidecar-v2/data/sidecar_v2.db
WAL模式"] + BACKUP["/opt/sidecar-v2/backups/
每日cron .backup + 7天保留"] + end + + subgraph "日志" + LOG["/var/log/sidecar-v2/
JSON结构化 + logrotate
10MB/文件 保留30天"] + end + + subgraph "防火墙" + FW["UFW: 仅192.168.1.0/24可访问 :9190/:9191"] + end + end + + subgraph "可选 Docker" + DOCKER["docker run sidecar-v2
-v /opt/sidecar-v2/data:/data
非root用户运行
HEALTHCHECK"] + end + + subgraph "外部" + PROM["Prometheus :9090
抓取 :9191 指标"] + end + + PROM -->|"scrape"| P9191 +``` + +--- + +## 3. 技术选型 + +### 3.1 核心框架 + +| 维度 | 方案 A: 扩展现有 V1 | 方案 B: 模块化重构 | **选型** | +|------|-------------------|-------------------|---------| +| 框架 | FastAPI(保持) | FastAPI(保持) | **FastAPI** | +| 优势 | 最小改动,快速交付 | 清晰模块边界 | 折中:分模块开发,渐进替换 | +| 风险 | 技术债累积 | 开发周期长 | 按池化层→流控层→统计层分阶段 | +| 工作量 | 3-5 天 | 7-10 天 | 6 阶段 69h | + +### 3.2 Provider 管理 + +| 方案 | 描述 | 选型 | +|------|------|------| +| A: Python 内存 dict + SQLite | 热数据在内存,持久化到 SQLite | ✅ **选用** | +| B: Redis + MySQL | 分布式支持但增加运维复杂度 | ❌ 过度设计(当前单机部署) | + +**多节点迁移触发条件**:活跃 Provider > 50 或管理节点 > 1 时,将 SQLite 迁移至 MySQL。当前单机部署满足需求。 + +### 3.3 数据库 + +| 方案 | 描述 | 选型 | +|------|------|------| +| A: SQLite(WAL 模式) | 单文件,零配置,事务安全 | ✅ **选用** | +| B: MySQL 8.0 | 团队熟悉,但增加运维 | ❌ 备选(Provider > 50 时迁移) | + +**SQLite 运维配置**: +- WAL 模式:`PRAGMA journal_mode=WAL` +- WAL 检查点:`wal_autocheckpoint=1000`(1000 页后自动 checkpoint) +- 定时 checkpoint:cron 每小时 `PRAGMA wal_checkpoint(TRUNCATE)` +- 备份策略:cron 每日 `.backup`,保留 7 天 +- 数据库监控:`PRAGMA integrity_check` 每日执行 + +### 3.4 负载均衡策略 + +| 策略 | 描述 | 适用场景 | 选型 | +|------|------|---------|------| +| Round Robin | 轮询分配 | 所有 Provider 同质 | 默认策略 | +| Weighted | 按 RPM 余量加权 | Provider RPM 不同 | ✅ **主池使用** | +| Least Connections | 最少活跃连接 | 响应时间差异大 | Fallback 池使用 | + +**实现**:`weight = (max_rpm - current_rpm_window) / max_rpm`,归一化后加权轮询。权重刷新周期:5-10 秒,平衡性能与响应速度。 + +**并发安全**:Provider 选择 + RPM 预占使用 `BEGIN IMMEDIATE` 事务包裹,确保原子性。 + +### 3.5 429 冷却策略(增强版) + +``` +检测到 429 响应 + ↓ +Provider 移入冷却池 + ↓ +冷却时间 = 30s × 2^(连续429次数 - 1) + • 首次 429: 30s + • 连续 2 次: 60s + • 连续 3 次: 120s + • 连续 4 次: 240s + • 连续 5 次: 480s + • 最大: 600s (10 分钟) + ↓ +冷却状态持久化到 cooldown_events 表 +服务重启后冷却状态可从 DB 恢复 + ↓ +冷却结束 → 放回主池(标记"观察中") + ↓ +恢复后首次请求成功 → 取消观察,重置冷却计数 +恢复后首次仍 429 → 回到冷却池,加倍冷却时间 +``` + +**紧急通道**:当主池 + Fallback 全部冷却时,选择冷却时间剩余最短的 1 个 Provider,以 10% RPM 继续尝试,避免完全断流。 + +**冷却预检**:降级到 Fallback 池前,检查主池是否有 Provider 剩余冷却时间 < 10s,可短暂等待其恢复。 + +### 3.6 队列控流机制 + +| 参数 | 配置 | 说明 | +|------|------|------| +| 队列模型 | FIFO + 优先级 | 继承 V1 的四级优先级(URGENT > HIGH > NORMAL > LOW) | +| 最大队列深度 | 500 | 超出后触发溢出策略 | +| 令牌等待超时 | 30s(默认) | 可配置 `SIDECAR_QUEUE_TIMEOUT` | +| 溢出策略 | REJECT (503) | 队列满时拒绝新请求,返回 503 + Retry-After | + +**溢出策略选择**: +- REJECT (503):返回 503,触发上游 OpenClaw 重试/降级 — **选用** +- PASSTHROUGH(直通):绕过限流直接转发 — V1 模式,V2 不推荐(可能导致上游被打爆) +- DROP_LOWEST(丢弃最低优先级):丢弃队列中最低优先级的请求 — 复杂度高,V3 考虑 + +**队列架构**:共享队列,不按 Provider 拆分。请求先入队,出队后由负载均衡器选择 Provider。这样可以用一个队列服务所有 Provider,简化实现。 + +### 3.7 池降级链路(增强版) + +``` +请求到达 + ↓ +主池 Provider 可用? + ├─ YES → 转发 + └─ NO(全部冷却/禁用) + ↓ + 主池有 Provider 即将恢复(剩余<10s)? + ├─ YES → 等待该 Provider 冷却结束 → 转发 + └─ NO + ↓ + Fallback 池 Provider 可用? + ├─ YES → 转发(Header: X-Sidecar-Degraded: fallback) + └─ NO(全部冷却/禁用) + ↓ + 紧急通道:1 Provider 以 10% RPM 尝试 + ├─ 成功 → 返回(Header: X-Sidecar-Degraded: emergency) + └─ 失败 + ↓ + 返回 503 + Header: X-Sidecar-Degraded: openclaw_fallback + ↓ + OpenClaw 收到 503 → 使用自身 fallback 链路 +``` + +### 3.8 WebUI 前后端 + +| 方案 | 描述 | 选型 | +|------|------|------| +| A: 纯 HTML + Alpine.js(V1 风格) | 轻量,无需构建 | ✅ **选用** | +| B: React SPA | 交互更强但增加构建步骤 | ❌ 过度工程 | + +**WebUI 安全**:Admin API 默认绑定 127.0.0.1,通过 Bearer Token (`SIDECAR_ADMIN_TOKEN`) 鉴权。如需外网访问,通过 nginx 反向代理添加 Basic Auth 或 OAuth2 Proxy。 + +--- + +## 4. Provider 健康检查 + +### 4.1 健康判定机制 + +采用**混合模式**:主动探测 + 被动统计。 + +#### 主动探测(定时 Health Probe) + +| 参数 | 值 | +|------|-----| +| 探测频率 | 每 60s | +| 探测方式 | GET /v1/models(轻量 API 调用) | +| 超时 | 10s | +| 探测对象 | 所有 `status=active` 的 Provider | + +#### 被动统计(成功率监控) + +| 参数 | 值 | +|------|-----| +| 统计窗口 | 滑动 5 分钟 | +| 不健康阈值 | 连续 5 次失败 / 5 分钟内成功率 < 50% | +| 恢复阈值 | 连续 3 次成功 + 成功率 > 80% | + +#### 健康状态机 + +``` +健康(healthy) + ↓ 连续5次失败 或 5min成功率<50% +降级(degraded) — 仍可服务, Dashboard 标黄 + ↓ 连续10次失败 或 5min成功率<20% +不健康(down) — 自动禁用, Dashboard 标红 + ↓ 连续3次成功 + 5min成功率>80% +恢复(healthy) — 重新激活 +``` + +### 4.2 主动探测 vs 被动统计 选择 + +| 方式 | 优势 | 劣势 | 决策 | +|------|------|------|------| +| 主动探测 | 及时发现不可用 Provider | 产生额外 API 调用 | 每 60s 轻量探测 | +| 被动统计 | 零额外开销 | 发现延迟(依赖实际请求) | 5min 滑动窗口实时统计 | + +**选型**:两者结合。主动探测覆盖请求低谷期,被动统计覆盖请求高峰期。 + +--- + +## 5. RPM 流控粒度 + +### 5.1 决策 + +| 方案 | 描述 | 选型 | +|------|------|------| +| A: Provider 级别 | 每个 Provider 一个 RPM 限制 | ✅ **V2 采用** | +| B: Provider + Model 级别 | 同一 Provider 下不同 Model 独立 RPM | V3 扩展 | + +**理由**:当前多 Provider 场景下,RPM 限制是按 API Key 授予的,而非按 Model 授予。同一 API Key 下的不同 Model 共享 RPM 配额是合理的默认行为。如需 Model 级别 RPM,可在 `providers.metadata` JSON 中扩展,不影响当前 Schema。 + +--- + +## 6. Provider 创建时的 Key 验证 + +创建 Provider 时必须经过 Key 有效性验证: + +``` +POST /api/v2/providers + ↓ +1. 基本信息校验(name, api_key, endpoint_url 必填) + ↓ +2. 发送 test call: GET {endpoint_url}/v1/models + Header: Authorization: Bearer {api_key} + ↓ + ├─ 200 OK → 验证通过 → 加密存储 → 返回 201 + ├─ 401/403 → Key 无效 → 返回 400 + "API Key 无效" + ├─ 超时 → endpoint_url 不可达 → 返回 400 + "无法连接到 Provider" + └─ 其他错误 → 返回 400 + 具体错误信息 + ↓ +3. RPM 自动探测(可选):验证通过后,读取响应中的 rate limit headers + 如无 headers,使用用户配置的 rpm_limit(默认 60) +``` + +--- + +## 7. 数据模型 + +### 7.1 ER 图 + +```mermaid +erDiagram + providers ||--o{ provider_usage_logs : has + providers ||--o{ cooldown_events : triggers + providers ||--o| provider_health : monitors + + providers { + string id PK + string name + string api_key + string endpoint_url + string model_prefix + string pool + string status + string source + int rpm_limit + int tpm_limit + float weight + float cost_per_1k + string cooldown_until + string metadata + } + + provider_usage_logs { + string id PK + string provider_id FK + string model + int prompt_tokens + int completion_tokens + int total_tokens + float cost + int request_count + int error_count + int avg_latency_ms + string hour_bucket + } + + cooldown_events { + string id PK + string provider_id FK + int consecutive_count + int cooldown_seconds + string response_summary + string started_at + string ended_at + } + + provider_health { + string provider_id PK + string state + int last_latency_ms + int last_status_code + float success_rate_5m + int consecutive_failures + } + + daily_stats { + string id PK + string date + string pool + int total_requests + int total_errors + int total_tokens + float total_cost + int unique_providers + } + + system_config { + string key PK + string value + string description + } +``` + +### 7.2 完整 DDL + +```sql +-- Provider 配置表(核心) +CREATE TABLE providers ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + api_key TEXT NOT NULL, -- AES-256-GCM 加密后存储 + endpoint_url TEXT NOT NULL, + model_prefix TEXT DEFAULT '', -- 如 "nvidia/", "openai/" + pool TEXT NOT NULL DEFAULT 'main' CHECK(pool IN ('main','fallback')), + status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active','disabled','cooldown')), + source TEXT NOT NULL DEFAULT 'db' CHECK(source IN ('env','db')), -- 来源标记 + rpm_limit INTEGER NOT NULL DEFAULT 60, + tpm_limit INTEGER DEFAULT NULL, -- NULL = 不限制 + weight REAL DEFAULT 1.0, + cost_per_1k_tokens REAL DEFAULT 0.0, + cooldown_until TEXT, -- 冷却结束时间戳(ISO 8601),NULL = 非冷却状态 + metadata JSON DEFAULT '{}', + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +-- Provider 用量日志(按小时分桶,支持并发安全写入) +CREATE TABLE provider_usage_logs ( + id TEXT PRIMARY KEY, + provider_id TEXT NOT NULL REFERENCES providers(id), + model TEXT DEFAULT 'unknown', + prompt_tokens INTEGER DEFAULT 0, + completion_tokens INTEGER DEFAULT 0, + total_tokens INTEGER DEFAULT 0, + cost REAL DEFAULT 0.0, + request_count INTEGER DEFAULT 0, + error_count INTEGER DEFAULT 0, + avg_latency_ms INTEGER DEFAULT 0, + hour_bucket TEXT NOT NULL, -- YYYY-MM-DD HH:00 按小时分桶 + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); +CREATE UNIQUE INDEX idx_usage_provider_hour + ON provider_usage_logs(provider_id, hour_bucket); + +-- 冷却事件日志 +CREATE TABLE cooldown_events ( + id TEXT PRIMARY KEY, + provider_id TEXT NOT NULL REFERENCES providers(id), + consecutive_count INTEGER NOT NULL DEFAULT 1, + cooldown_seconds INTEGER NOT NULL, + response_summary TEXT DEFAULT '', -- 触发冷却的 HTTP 响应摘要(前500字符) + started_at TEXT NOT NULL DEFAULT (datetime('now')), + ended_at TEXT +); +CREATE INDEX idx_cooldown_provider_time + ON cooldown_events(provider_id, started_at); + +-- Provider 健康状态 +CREATE TABLE provider_health ( + provider_id TEXT PRIMARY KEY REFERENCES providers(id), + state TEXT NOT NULL DEFAULT 'healthy' CHECK(state IN ('healthy','degraded','down')), + last_latency_ms INTEGER DEFAULT 0, + last_status_code INTEGER DEFAULT 200, + success_rate_5m REAL DEFAULT 1.0, -- 5分钟滑动窗口成功率 + consecutive_failures INTEGER DEFAULT 0, + last_check_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +-- 系统配置 KV +CREATE TABLE system_config ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL, + description TEXT DEFAULT '', + updated_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +-- 每日汇总统计(cron 定时聚合,非实时写入) +CREATE TABLE daily_stats ( + id TEXT PRIMARY KEY, + date TEXT NOT NULL, + pool TEXT NOT NULL CHECK(pool IN ('main','fallback')), + total_requests INTEGER DEFAULT 0, + total_errors INTEGER DEFAULT 0, + total_tokens INTEGER DEFAULT 0, + total_cost REAL DEFAULT 0.0, + unique_providers INTEGER DEFAULT 0, + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); +CREATE UNIQUE INDEX idx_daily_date_pool ON daily_stats(date, pool); + +-- 并发安全写入示例(避免竞态条件) +-- 用量日志写入使用 UPSERT: +-- INSERT INTO provider_usage_logs (...) VALUES (...) +-- ON CONFLICT(provider_id, hour_bucket) DO UPDATE SET +-- total_tokens = total_tokens + excluded.total_tokens, +-- cost = cost + excluded.cost, +-- ...; +``` + +--- + +## 8. API 设计 + +### 8.1 代理 API(V1 兼容) + +| 方法 | 路径 | 说明 | +|------|------|------| +| POST | `/v1/chat/completions` | Chat Completions 代理 | +| POST | `/v1/completions` | Completions 代理 | +| POST | `/v1/embeddings` | Embeddings 代理 | +| POST | `/v1/responses` | Responses 代理(新增) | +| GET | `/v1/models` | 模型列表(聚合所有 Provider) | + +### 8.2 请求 Headers + +``` +X-Sidecar-Pool: main | fallback # 指定目标池(默认 main) +X-Sidecar-Provider: # 指定 Provider(调试用) +X-Priority: normal | high | urgent # 优先级 +``` + +### 8.3 Provider 管理 API + +```yaml +# Provider CRUD +GET /api/v2/providers # 列出 Provider + ?pool=main&status=active # 筛选 +POST /api/v2/providers # 添加 Provider(含 test call 验证) + Body: {name, api_key, endpoint_url, pool, rpm_limit, model_prefix, ...} +GET /api/v2/providers/{id} # 查看详情(api_key 脱敏显示) +PUT /api/v2/providers/{id} # 更新配置 +DELETE /api/v2/providers/{id} # 删除 Provider +POST /api/v2/providers/{id}/enable # 启用 +POST /api/v2/providers/{id}/disable # 禁用 +POST /api/v2/providers/{id}/test # 连通性测试 + +# 池状态 +GET /api/v2/pools # 池概览 +GET /api/v2/pools/{pool}/providers # 指定池 Provider 列表 +GET /api/v2/cooldowns # 当前冷却 Provider + +# 统计 +GET /api/v2/stats/overview # 总览(今日/本周/本月) +GET /api/v2/stats/providers/{id} # 单 Provider 统计 +GET /api/v2/stats/daily # 按日汇总 ?days=7 +GET /api/v2/stats/tokens # Token 趋势 + +# 配置 +GET /api/v2/config # 系统配置 +PUT /api/v2/config # 更新配置 + +# 健康 +GET /health # 存活检查 (liveness) +GET /health/ready # 就绪检查 (readiness — DB + Provider池) +GET /status # 调试状态(限流器+队列+冷却) + +# Dashboard +GET /api/dashboard # Dashboard HTML +GET /api/dashboard/stream # SSE 实时推送 +``` + +### 8.4 关键 API 请求/响应示例 + +**POST /api/v2/providers(添加 Provider)** + +请求: +```json +{ + "name": "NVIDIA Key 2", + "api_key": "nvapi-xxxxxxxxxxxx", + "endpoint_url": "https://integrate.api.nvidia.com/v1", + "pool": "main", + "rpm_limit": 40, + "model_prefix": "nvidia/", + "cost_per_1k_tokens": 0.001 +} +``` + +响应(成功): +```json +{ + "id": "p_abc123", + "name": "NVIDIA Key 2", + "api_key": "nvapi-*****-xxxx", // 脱敏显示 + "pool": "main", + "status": "active", + "rpm_limit": 40, + "test_result": "ok", + "test_latency_ms": 234 +} +``` + +响应(Key 无效): +```json +{ + "error": "api_key_invalid", + "message": "API Key 验证失败:上游返回 401 Unauthorized" +} +``` + +**GET /api/v2/pools** + +响应: +```json +{ + "main": { + "total": 5, + "active": 3, + "cooldown": 1, + "disabled": 1, + "total_rpm_capacity": 150, + "current_load_rpm": 87 + }, + "fallback": { + "total": 2, + "active": 2, + "cooldown": 0, + "disabled": 0, + "total_rpm_capacity": 40, + "current_load_rpm": 0 + } +} +``` + +--- + +## 9. Dashboard 指标清单 + +### 9.1 实时状态面板 + +| 面板 | 指标 | 展示方式 | +|------|------|----------| +| Provider 状态卡片 | 名称、池、状态(绿/黄/红)、RPM利用率、成功率 | 卡片网格 | +| 池概览 | 各池 Provider 数量、总 RPM 容量、当前负载 | 数字仪表 | +| 冷却列表 | 冷却中 Provider、剩余冷却时间、历史次数 | 列表 + 倒计时 | + +### 9.2 趋势图表 + +| 图表 | 数据源 | 展示方式 | +|------|--------|----------| +| Token 用量趋势 | provider_usage_logs | 折线图(按小时/天) | +| 调用次数趋势 | provider_usage_logs.request_count | 柱状图 | +| 费用走势 | provider_usage_logs.cost | 折线图 + 累计 | +| 成功率趋势 | provider_health.success_rate_5m | 折线图 | +| 各 Provider 占比 | provider_usage_logs | 饼图 | + +### 9.3 历史记录 + +| 面板 | 数据源 | 展示方式 | +|------|--------|----------| +| 冷却事件历史 | cooldown_events | 时间线列表 | +| 请求日志 | provider_usage_logs | 分页表格 | + +### 9.4 告警机制(V3 扩展) + +当前 V2 依赖 Dashboard 被动查看,V3 将增加主动告警: + +- Provider 全部冷却 → Webhook / 飞书通知 +- 单 Provider RPM 利用率 > 80% → 预警 +- 日费用突增 > 50% → 预警 +- Provider 连续不健康 > 10 分钟 → 通知 + +V2 架构已在 Prometheus metrics 和 `system_config` 中预留告警配置接口。 + +--- + +## 10. 安全设计 + +### 10.1 API Key 加密存储 + +| 参数 | 配置 | +|------|------| +| 加密算法 | AES-256-GCM | +| 密钥来源 | 环境变量 `SIDECAR_ENCRYPTION_KEY`(必填,32 字节 hex) | +| 密钥生成 | 部署时:`openssl rand -hex 32` | +| 密钥备份 | 备份到安全位置(Bitwarden / 密码管理器),标注于部署 SOP | +| 密钥丢失 | 所有已存储 API Key 不可恢复,需重新添加 | + +### 10.2 安全策略 + +| 策略 | 实现 | +|------|------| +| API Key 加密 | AES-256-GCM,密钥从环境变量注入,不存储在 DB 中 | +| Admin API 鉴权 | Bearer Token (`SIDECAR_ADMIN_TOKEN`) | +| 内网绑定 | 默认绑定 127.0.0.1,对外端口需显式配置 | +| 日志脱敏 | API Key 只显示前 6 位 + 后 4 位(如 `nvapi-*****-xxxx`) | +| systemd 加固 | ProtectSystem=strict, NoNewPrivileges=true | +| Docker 非 root | USER 1000:1000 | +| WebUI 远程访问 | 通过 nginx 反向代理 + Basic Auth / OAuth2 Proxy | + +### 10.3 API Key 批量泄露远期防护 + +当前单机部署下,加密密钥和密文在同一台机器上。远期方案: +- 使用 HashiCorp Vault 或云 KMS 管理加密密钥 +- 密钥定期轮转(re-encrypt 已有 API Key) + +--- + +## 11. 运维设计 + +### 11.1 端口规划 + +| 端口 | 用途 | 绑定 | +|------|------|------| +| 9190 | API 代理 + WebUI + Admin API | 127.0.0.1(默认) | +| 9191 | Prometheus Metrics | 127.0.0.1(默认) | + +防火墙规则: +```bash +sudo ufw allow from 192.168.1.0/24 to any port 9190 +sudo ufw allow from 192.168.1.0/24 to any port 9191 +sudo ufw deny 9190 # 禁止外网 +sudo ufw deny 9191 +``` + +### 11.2 日志管理 + +| 配置 | 值 | +|------|-----| +| 日志格式 | JSON 结构化(structlog) | +| 输出目标 | systemd 部署→文件 `/var/log/sidecar-v2/`; Docker→stdout | +| 日志轮转 | logrotate: 10MB/文件, 保留 30 天 | +| 日志级别 | INFO(默认),可配置 LOG_LEVEL=DEBUG | + +### 11.3 SQLite 备份策略 + +```bash +# /opt/sidecar-v2/scripts/backup.sh +# 每日 cron: 0 3 * * * /opt/sidecar-v2/scripts/backup.sh + +#!/bin/bash +DB="/opt/sidecar-v2/data/sidecar_v2.db" +BACKUP_DIR="/opt/sidecar-v2/backups" +DATE=$(date +%Y%m%d_%H%M%S) +BACKUP_FILE="$BACKUP_DIR/sidecar_v2_$DATE.db" + +# WAL checkpoint 确保一致性 +sqlite3 "$DB" "PRAGMA wal_checkpoint(TRUNCATE)" + +# 备份 +sqlite3 "$DB" ".backup $BACKUP_FILE" + +# 保留 7 天 +find "$BACKUP_DIR" -name "sidecar_v2_*.db" -mtime +7 -delete +``` + +### 11.4 SQLite 数据库监控 + +新增 Prometheus 指标: + +| 指标 | 类型 | 说明 | +|------|------|------| +| `sidecar_db_size_bytes` | Gauge | SQLite 文件大小 | +| `sidecar_db_wal_size_bytes` | Gauge | WAL 文件大小 | +| `sidecar_db_integrity_ok` | Gauge | integrity_check 结果 (0/1) | + +系统级监控: +- 磁盘使用率:> 80% 告警,> 90% 严重告警 +- IO 延迟:> 100ms 告警 + +### 11.5 配置热加载 + +- Provider CRUD 操作自动更新内存缓存(无需重启) +- 系统配置项(`system_config` 表)支持热加载 +- 环境变量变更需重启服务 + +--- + +## 12. V1 → V2 迁移 + +### 12.1 V1 兼容性 Checklist + +| 兼容项 | V1 行为 | V2 行为 | 状态 | +|--------|---------|---------|------| +| `/v1/chat/completions` | 代理到 NVIDIA | 代理到 Provider 池 | ✅ 兼容 | +| `/v1/completions` | 代理到 NVIDIA | 代理到 Provider 池 | ✅ 兼容 | +| `/v1/embeddings` | 代理到 NVIDIA | 代理到 Provider 池 | ✅ 兼容 | +| `/health` | 存活检查 | 存活检查 | ✅ 兼容 | +| `/health/ready` | 上游连通性 | DB + Provider 池就绪 | ⚠️ 增强 | +| `/status` | 调试状态 | 增强:池+冷却状态 | ⚠️ 增强 | +| `/metrics` | :9191 Prometheus | :9191 增强指标 | ⚠️ 增强 | +| `SIDECAR_API_KEY` | NVIDIA Key | 自动创建单 Provider | ✅ 兼容 | +| `SIDECAR_RATE_RPM` | 全局 RPM | 单 Provider RPM | ✅ 兼容 | +| `SIDECAR_UPSTREAM` | 上游 URL | 单 Provider endpoint | ✅ 兼容 | +| `SIDECAR_HOST/PORT` | 监听配置 | 监听配置 | ✅ 兼容 | +| `SIDECAR_QUEUE_MAX` | 队列容量 | 队列容量 | ✅ 兼容 | +| `X-Priority` Header | 优先级 | 优先级 | ✅ 兼容 | +| 日志格式 | JSON structlog | JSON structlog | ✅ 兼容 | +| Prometheus metric 命名 | sidecar_* | sidecar_* | ✅ 兼容 | + +### 12.2 迁移步骤 + +``` +1. 停止 V1 服务 + systemctl stop nvidia-sidecar + +2. 备份 V1 配置 + cp /opt/nvidia-sidecar/.env /tmp/nvidia-sidecar.env.bak + +3. 安装 V2 + pip install sidecar-v2 + +4. 配置加密密钥 + export SIDECAR_ENCRYPTION_KEY=$(openssl rand -hex 32) + # 备份此密钥到密码管理器! + +5. 首次启动(自动检测无 DB → 从环境变量创建单 Provider) + systemctl start sidecar-v2 + +6. 验证 + curl http://127.0.0.1:9190/health + curl http://127.0.0.1:9190/api/v2/providers + # 应看到从 SIDECAR_API_KEY 创建的 source=env Provider + +7. 通过 WebUI 添加更多 Provider + +8. 监控观察 24h,确认无异常 + +回滚方案: + systemctl stop sidecar-v2 + systemctl start nvidia-sidecar +``` + +### 12.3 回滚方案 + +如果 V2 出现问题: +```bash +# 1. 停止 V2 +systemctl stop sidecar-v2 + +# 2. 恢复 V1 +systemctl start nvidia-sidecar + +# 3. 清理 V2 数据(可选,保留用于排查) +# rm /opt/sidecar-v2/data/sidecar_v2.db + +# V1 纯环境变量驱动,不受 V2 SQLite DB 影响,回滚无数据损失 +``` + +--- + +## 13. Prometheus Metrics 增强 + +### 13.1 继承 V1 指标 + +| 指标 | 说明 | +|------|------| +| `sidecar_requests_total` | 总请求数 | +| `sidecar_errors_total` | 总错误数 | +| `sidecar_latency_seconds` | 延迟分布 | +| `sidecar_queue_depth` | 当前队列深度 | +| `sidecar_token_bucket_available` | 令牌桶可用令牌 | + +### 13.2 V2 新增指标 + +| 指标 | 类型 | Label | 说明 | +|------|------|-------|------| +| `sidecar_provider_requests_total` | Counter | provider_id, pool, status_code | 每 Provider 请求数 | +| `sidecar_provider_latency_seconds` | Histogram | provider_id | 每 Provider 延迟 | +| `sidecar_provider_rpm_current` | Gauge | provider_id | 当前 RPM | +| `sidecar_provider_cooldown_active` | Gauge | provider_id | 冷却中=1 | +| `sidecar_cooldown_events_total` | Counter | provider_id | 冷却触发次数 | +| `sidecar_pool_degradation_total` | Counter | from_pool, to_pool | 降级事件 | +| `sidecar_emergency_channel_total` | Counter | provider_id | 紧急通道使用次数 | +| `sidecar_db_size_bytes` | Gauge | — | SQLite 文件大小 | +| `sidecar_db_wal_size_bytes` | Gauge | — | WAL 文件大小 | +| `sidecar_db_integrity_ok` | Gauge | — | integrity_check 结果 | + +--- + +## 14. 核心模块设计 + +### 14.1 模块结构 + +``` +sidecar_v2/ +├── server.py # FastAPI 入口 + 生命周期 +├── config.py # 系统配置管理 +├── crypto.py # AES-256-GCM 加解密 +│ +├── core/ +│ ├── provider.py # Provider 数据模型 + CRUD 服务 +│ ├── pool.py # 池管理器(main/fallback/cooldown) +│ ├── router.py # 负载均衡路由器 +│ ├── rate_limiter.py # Per-Provider Token Bucket +│ ├── cooldown.py # 429 冷却管理(含紧急通道) +│ └── queue.py # FIFO 优先级队列 +│ +├── proxy/ +│ ├── handler.py # 请求代理处理器 +│ └── retry.py # 429 重试 + 降级逻辑 +│ +├── storage/ +│ ├── db.py # SQLite/SQLAlchemy 连接管理 +│ ├── models.py # ORM 模型 +│ └── backup.py # 备份逻辑 +│ +├── stats/ +│ ├── collector.py # 实时统计采集 +│ ├── aggregator.py # 按时段聚合(小时桶 + 日汇总 cron) +│ └── cost.py # 费用计算 +│ +├── health.py # 健康检查(主动探测 + 被动统计) +├── metrics.py # Prometheus 指标 +│ +├── webui/ +│ ├── dashboard.py # Dashboard 页面 + SSE +│ └── admin.py # Admin API 路由 +│ +└── templates/ + ├── dashboard.html + └── static/ + ├── app.js + └── style.css +``` + +### 14.2 并发控制关键代码模式 + +Provider 选择 + RPM 预占必须原子化: + +```python +# router.py — Provider 选择(伪代码) +def select_provider(pool: str, model: str) -> Provider: + with db_session() as session: + # BEGIN IMMEDIATE 事务确保原子性 + session.execute(text("BEGIN IMMEDIATE")) + + providers = get_active_providers(session, pool) + for p in weighted_round_robin(providers): + if rate_limiter.acquire(p.id, estimated_tokens): + # 更新当前 RPM 计数 + increment_current_rpm(session, p.id) + session.commit() + return p + session.rollback() + raise AllProvidersExhausted() +``` + +用量日志并发写入(UPSERT 避免竞态): + +```python +# collector.py — 用量记录 +def record_usage(provider_id, tokens, cost, latency, status_code): + with db_session() as session: + session.execute(text(""" + INSERT INTO provider_usage_logs + (id, provider_id, model, total_tokens, cost, + request_count, error_count, avg_latency_ms, hour_bucket) + VALUES + (:id, :pid, :model, :tokens, :cost, 1, :err, :lat, :bucket) + ON CONFLICT(provider_id, hour_bucket) DO UPDATE SET + total_tokens = total_tokens + excluded.total_tokens, + cost = cost + excluded.cost, + request_count = request_count + excluded.request_count, + error_count = error_count + excluded.error_count, + avg_latency_ms = (avg_latency_ms * request_count + excluded.avg_latency_ms) + / (request_count + excluded.request_count) + """), {...}) +``` + +--- + +## 15. 非功能需求 + +### 15.1 性能目标 + +| 指标 | 目标值 | 测量方式 | +|------|--------|---------| +| 代理延迟增加 | < 5ms (P95) | 对比直连 vs Sidecar 代理 | +| 并发处理能力 | 100 req/s | P1 完成后并发压测验证 | +| Provider 池查询延迟 | < 1ms | 内存缓存 + SQLite | +| Dashboard SSE 延迟 | < 1s | 实时推送间隔 | +| 启动时间 | < 3s | systemd 启动计时 | + +### 15.2 可用性 + +| 指标 | 目标 | +|------|------| +| 主池可用率 | > 99.5%(含冷却恢复) | +| Fallback 降级触发率 | < 5% | +| 完全不可用(503)概率 | < 0.1% | +| 配置热加载 | 支持(Provider CRUD 无需重启) | +| 服务重启恢复 | 冷却状态从 DB 恢复,不丢失上下文 | + +### 15.3 可观测性 + +| 维度 | 工具 | +|------|------| +| 结构化日志 | structlog(JSON 格式) | +| 实时指标 | Prometheus(独立 :9191 端口,13 项指标) | +| Dashboard | SSE 实时推送 + Chart.js(5 个面板) | +| 健康检查 | `/health` + `/health/ready` + `/status` | + +--- + +## 16. 开发排期(修订版) + +### 新增 P0:文档推送 + 环境准备(3h) + +- [ ] 推送架构文档 + 图表到 Git +- [ ] 确认开发环境(Python 3.12 + 依赖) + +### Phase 1: 数据层 + Provider CRUD(12h) + +- [ ] SQLite 数据库 + SQLAlchemy ORM + 6 张表 DDL +- [ ] AES-256-GCM 加密模块(密钥从环境变量注入) +- [ ] Provider CRUD API(添加含 test call 验证) +- [ ] 并发压测(100 QPS+ 验证 SQLite WAL) + +### Phase 2: 池管理 + 负载均衡(14h) + +- [ ] PoolManager(main / fallback / cooldown 三池) +- [ ] Weighted RR(权重 5-10s 刷新) +- [ ] 429 冷却 + 指数退避 + 紧急通道 +- [ ] 降级链路(main → fallback 预检 → emergency → 503) +- [ ] Prometheus 基础指标接入 +- [ ] Provider 健康检查(主动探测 + 被动统计) + +### Phase 3: 流控 + 统计(12h) + +- [ ] Per-Provider Token Bucket + FIFO 优先级队列 +- [ ] 请求代理处理器(池选 + 流控 + 重试) +- [ ] 用量统计(UPSERT 写入 + 小时桶 + cron 日聚合) +- [ ] Prometheus V2 新增指标 + +### Phase 4: WebUI + Dashboard(10h) + +- [ ] Admin 管理页面(Provider 列表/添加/编辑/测试) +- [ ] Dashboard(5 个面板:状态卡片、趋势图表、历史记录) +- [ ] SSE 实时推送 +- [ ] 系统配置页面 + +### Phase 5: 测试 + 集成(12h) + +- [ ] 单元测试(各核心模块) +- [ ] 集成测试(多 Provider 429 场景模拟) +- [ ] V1 兼容性测试(10 项兼容 checklist) +- [ ] 并发压测(100 QPS 持续 5 分钟) + +### Phase 6: 部署 + 文档(8h) + +- [ ] systemd service + Dockerfile +- [ ] 部署 SOP(初始化、升级、回滚) +- [ ] SQLite 备份脚本 + cron +- [ ] V1→V2 迁移文档 +- [ ] API 文档(OpenAPI Schema) +- [ ] 防火墙 + 日志轮转配置 + +| Phase | 内容 | 工时 | +|-------|------|------| +| P0 | 文档推送 + 环境准备 | 3h | +| P1 | 数据层 + Provider CRUD | 12h | +| P2 | 池管理 + 负载均衡 | 14h | +| P3 | 流控 + 统计 | 12h | +| P4 | WebUI + Dashboard | 10h | +| P5 | 测试 + 集成 | 12h | +| P6 | 部署 + 文档 | 8h | +| **合计** | | **71h (~12 天)** | + +--- + +## 17. 协作节点 + +| 阶段 | 协作 Agent | 事项 | +|------|-----------|------| +| P0 完成 | 严维序 | 审查部署拓扑,确认运维可行性 | +| P2 完成 | 严维序 | Prometheus metrics 接入 + 告警规则 | +| P5 阶段 | 沈路明 | 按 PRD 验收标准做功能验收 | +| P6 完成 | 严维序 | 灰度部署 + 监控确认 | + +--- + +## 18. 技术决策摘要 + +| 决策 | 选择 | 关键理由 | +|------|------|---------| +| 数据库 | SQLite WAL | 零运维,满足当前 50 Provider 规模,预留 MySQL 迁移 | +| 负载均衡 | Weighted RR (5-10s刷新) | 按 RPM 余量动态加权,平衡性能与响应 | +| 冷却策略 | 指数退避 30s→600s + 紧急通道 | 平衡恢复速度与保护上游,避免完全断流 | +| 冷却持久化 | SQLite cooldown_events 表 | 服务重启后冷却状态可恢复 | +| API Key 存储 | AES-256-GCM,密钥 env var | 密钥不落盘,满足安全基线 | +| RPM 粒度 | Provider 级别 | V2 采用,Model 级别 V3 扩展 | +| 队列模型 | FIFO + 优先级,容量 500 | 共享队列,溢出 REJECT (503) | +| 健康检查 | 主动探测 + 被动统计 | 双重保障,覆盖请求低谷和高分期 | +| WebUI | Alpine.js + Chart.js | 与 V1 一致,零构建步骤 | +| 统计写入 | UPSERT (ON CONFLICT) | 并发安全,避免竞态 | +| Provider 选择 | BEGIN IMMEDIATE 事务 | 原子化选 Provider + 预占 RPM | +| 加密密钥备份 | 密码管理器 | 密钥丢失 = 所有 API Key 不可恢复 | +| SQLite 备份 | cron 每日 `.backup`,保留 7 天 | 灾备基础,防数据丢失 | +| 部署方式 | systemd(主)+ Docker(可选) | 内网环境优先 systemd | + +--- + +## 19. 风险评估 + +| 风险 | 概率 | 影响 | 应对措施 | +|------|------|------|---------| +| SQLite 并发瓶颈 | 低 | 中 | WAL 模式 + P1 完成后压测;监控 DB 锁等待 | +| API Key 泄露 | 低 | 高 | AES-256 + 日志脱敏 + Admin Token + systemd 加固 | +| 429 风暴导致全冷却 | 中 | 高 | 紧急通道 10% RPM + 冷却时间上限 600s | +| 权重计算滞后 | 低 | 中 | 滑动窗口计数 + 5-10s 刷新周期 | +| 加密密钥丢失 | 低 | 高 | 备份到密码管理器,标注于部署 SOP | +| 服务重启丢失冷却状态 | 中 | 中 | cooldown_events 表持久化 + 启动时恢复 | +| V1→V2 迁移中断 | 低 | 中 | 回滚方案:重启 V1 即可,V1 纯环境变量驱动 | +| SSE 连接数过多 | 低 | 低 | 单 Dashboard 连接,1s 推送间隔 | +| 磁盘占满 | 低 | 中 | SQLite 备份保留 7 天,日志轮转 10MB/30 天 | + +--- + +## 20. 下游交付说明 + +### 20.1 给 costcodev(徐聪) + +- Provider 模型和数据库 Schema 严格按本文档 DDL 实现(v2.0 修订版) +- 429 冷却使用指数退避 + 紧急通道 + 冷却预检 +- Provider 选择 + RPM 预占使用 `BEGIN IMMEDIATE` 事务 +- 用量日志使用 `INSERT ... ON CONFLICT DO UPDATE` +- V1 兼容性按 13 项 checklist 逐项验证 +- API Key 加解密通过 `crypto.py` 统一处理 +- 健康检查实现主动探测 + 被动统计混合模式 + +### 20.2 给 opengineer(严维序) + +- 端口:9190 (API+WebUI+Admin), 9191 (metrics) +- 数据库:`/opt/sidecar-v2/data/sidecar_v2.db` (WAL 模式) +- 环境变量:`SIDECAR_ENCRYPTION_KEY`(加密密钥)、`SIDECAR_ADMIN_TOKEN`(Admin Token) +- SQLite 备份:cron 每日 03:00,保留 7 天 +- 日志轮转:10MB/文件,保留 30 天 +- 防火墙:仅 192.168.1.0/24 可访问 +- 部署方式:systemd 主方案,Docker 备选 + +--- + +> **架构格言**:好的架构就像城市的下水道——平时看不见,但一旦出问题就是灾难。 +> — 梁思筑,2026-06-25 (v2.0 评审修订版) \ No newline at end of file diff --git a/docs/architecture/sidecar-v2-dataflow.mmd b/docs/architecture/sidecar-v2-dataflow.mmd new file mode 100644 index 0000000..b517f40 --- /dev/null +++ b/docs/architecture/sidecar-v2-dataflow.mmd @@ -0,0 +1,58 @@ +sequenceDiagram + participant OC as OpenClaw + participant GW as API Gateway + participant LB as 负载均衡器 + participant QM as 队列管理器 + participant RL as Rate Limiter + participant P as Provider + participant CD as Cooldown Detector + participant ST as 统计引擎 + + OC->>GW: POST /v1/chat/completions + GW->>LB: 路由到目标池 + + Note over LB: Weighted RR 5-10s刷新
weight=(max_rpm-current_rpm)/max_rpm + + LB->>RL: BEGIN IMMEDIATE 事务 检查 RPM + 预占 + + alt RPM 不足 + RL->>QM: 入队等待 超时30s + QM-->>RL: 令牌可用 + end + + RL-->>LB: 允许转发 + + LB->>P: 转发请求 + P-->>LB: 响应 + + alt 200 OK + LB->>ST: INSERT ON CONFLICT 记录 usage_logs + LB-->>GW: 正常响应 + else 429 Too Many Requests + LB->>CD: 上报429 + CD->>P: 移入冷却池 cooldown_until=now+30s×2^n + + LB->>LB: 重新选择 Provider B + + alt Provider B 正常 + LB->>P: 转发到 Provider B + P-->>LB: 200 OK + end + + alt 主池全部冷却 + Note over LB: 降级 Fallback 池
检查即将恢复的Provider
剩余<10s 等待 + + alt Fallback 可用 + LB->>P: 转发 Fallback Provider + P-->>LB: 200 OK +降级标记 + else Fallback 也全冷却 + LB->>P: 紧急通道 1 Provider 10% RPM + alt 紧急通道成功 + P-->>LB: 200 OK + else + LB-->>OC: 503 Service Unavailable + OC->>OC: OpenClaw 自身 fallback + end + end + end + end \ No newline at end of file diff --git a/docs/architecture/sidecar-v2-dataflow.png b/docs/architecture/sidecar-v2-dataflow.png new file mode 100644 index 0000000..f64308e Binary files /dev/null and b/docs/architecture/sidecar-v2-dataflow.png differ diff --git a/docs/architecture/sidecar-v2-er.mmd b/docs/architecture/sidecar-v2-er.mmd new file mode 100644 index 0000000..b823a14 --- /dev/null +++ b/docs/architecture/sidecar-v2-er.mmd @@ -0,0 +1,71 @@ +erDiagram + providers ||--o{ provider_usage_logs : has + providers ||--o{ cooldown_events : triggers + providers ||--o| provider_health : monitors + + providers { + string id PK + string name + string api_key + string endpoint_url + string model_prefix + string pool + string status + string source + int rpm_limit + int tpm_limit + float weight + float cost_per_1k + string cooldown_until + string metadata + } + + provider_usage_logs { + string id PK + string provider_id FK + string model + int prompt_tokens + int completion_tokens + int total_tokens + float cost + int request_count + int error_count + int avg_latency_ms + string hour_bucket + } + + cooldown_events { + string id PK + string provider_id FK + int consecutive_count + int cooldown_seconds + string response_summary + string started_at + string ended_at + } + + provider_health { + string provider_id PK + string state + int last_latency_ms + int last_status_code + float success_rate_5m + int consecutive_failures + } + + daily_stats { + string id PK + string date + string pool + int total_requests + int total_errors + int total_tokens + float total_cost + int unique_providers + } + + system_config { + string key PK + string value + string description + } \ No newline at end of file diff --git a/docs/architecture/sidecar-v2-er.png b/docs/architecture/sidecar-v2-er.png new file mode 100644 index 0000000..f01e728 Binary files /dev/null and b/docs/architecture/sidecar-v2-er.png differ diff --git a/docs/architecture/sidecar-v2-topology.mmd b/docs/architecture/sidecar-v2-topology.mmd new file mode 100644 index 0000000..056ffef --- /dev/null +++ b/docs/architecture/sidecar-v2-topology.mmd @@ -0,0 +1,58 @@ +flowchart TB + subgraph OC["OpenClaw Gateway"] + OC_SCHED["OpenClaw 调度器"] + OC_FB["OpenClaw Fallback
传统配置链路"] + end + + subgraph SIDECAR["Sidecar V2 systemd/Docker"] + direction TB + + subgraph ENTRY["入口层"] + GW["API Gateway :9190
FastAPI + 路由匹配"] + end + + subgraph CORE["核心调度层"] + LB["负载均衡器
Weighted RR 5-10s刷新"] + QM["队列管理器
FIFO + 优先级
容量500 + 溢出策略"] + end + + subgraph POOLS["Provider 池层"] + MP["主池 Main Pool"] + FP["Fallback 池"] + CP["冷却池
Cooldown Pool"] + end + + subgraph FLOW["流控层"] + RL["Rate Limiter
Per-Provider Token Bucket"] + CD["Cooldown Detector
429检测+指数退避
+紧急通道10%RPM"] + end + + subgraph STATS["存储与统计层"] + MT["Metrics :9191
Prometheus"] + ST["统计引擎
Token/费用/调用量"] + DB[("SQLite WAL
sidecar_v2.db
+ cron备份")] + end + + subgraph WEBUI["WebUI 层 :9190"] + UI["Dashboard
SSE 实时推送"] + AP["Admin API
Provider CRUD
Bearer Token 鉴权"] + end + end + + OC_SCHED --> GW + GW --> LB + LB --> QM + QM --> RL + RL --> MP + RL --> FP + MP -.->|"429 触发冷却"| CP + MP -->|"全部冷却"| FP + FP -->|"全部冷却"| OC_FB + CP -.->|"冷却结束恢复"| MP + RL --> CD + CD -.->|"紧急通道 10% RPM"| MP + LB --> MT + MT --> ST + ST --> DB + DB --> UI + AP --> DB \ No newline at end of file diff --git a/docs/architecture/sidecar-v2-topology.png b/docs/architecture/sidecar-v2-topology.png new file mode 100644 index 0000000..32a97ae Binary files /dev/null and b/docs/architecture/sidecar-v2-topology.png differ