82edded30c
Incorporated feedback from 4 reviewers: - 徐聪: AES key management, emergency channel, concurrency control, DDL indexes - 陆怀瑾: P0 phase, schedule buffer, deployment topology, V1 compat checklist - 严维序: SQLite backup, monitoring, cooldown persistence, port plan, rollback - 沈路明: queue design, health check, per-model RPM decision, key validation, dashboard panels Key additions: + Queue flow control design (FIFO + priority, capacity 500, REJECT overflow) + Provider health check (active probe + passive stats hybrid) + Per-model RPM decision (Provider-level V2, Model-level V3) + Key validation on add (test call with error feedback) + AES key management (SIDECAR_ENCRYPTION_KEY env var, backup SOP) + Emergency channel (10% RPM during full cooldown) + SQLite backup strategy (cron .backup, 7-day retention) + SQLite monitoring Prometheus metrics (db_size, wal_size, integrity) + Full DDL with indexes (ON CONFLICT, BEGIN IMMEDIATE patterns) + Dashboard panel list (5 panels: status, trends, history) + V1 compatibility checklist (13 items) + V1->V2 migration SOP with rollback plan + Deployment topology (systemd + Docker, port plan, firewall) + Log aggregation policy (logrotate: 10MB/30days) + Schedule revised: 71h/12days (added P0 + buffer) Co-authored-by: multica-agent <github@multica.ai>
1242 lines
40 KiB
Markdown
1242 lines
40 KiB
Markdown
# 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<br/>传统配置链路]
|
||
end
|
||
|
||
subgraph "Sidecar V2 (systemd / Docker)"
|
||
direction TB
|
||
|
||
subgraph "入口层"
|
||
GW["API Gateway :9190<br/>FastAPI + 路由匹配"]
|
||
end
|
||
|
||
subgraph "核心调度层"
|
||
LB["负载均衡器<br/>Weighted RR 5-10s刷新"]
|
||
QM["队列管理器<br/>FIFO + 优先级<br/>容量500 + 溢出策略"]
|
||
end
|
||
|
||
subgraph "Provider 池层"
|
||
direction LR
|
||
MP["主池 Main Pool"]
|
||
FP["Fallback 池"]
|
||
CP["冷却池<br/>Cooldown Pool"]
|
||
end
|
||
|
||
subgraph "流控层"
|
||
RL["Rate Limiter<br/>Per-Provider Token Bucket"]
|
||
CD["Cooldown Detector<br/>429检测+指数退避<br/>+紧急通道10%RPM"]
|
||
end
|
||
|
||
subgraph "存储与统计层"
|
||
MT["Metrics :9191<br/>Prometheus"]
|
||
ST["统计引擎<br/>Token/费用/调用量"]
|
||
DB[("SQLite WAL<br/>sidecar_v2.db<br/>+ cron备份")]
|
||
end
|
||
|
||
subgraph "WebUI 层 :9190"
|
||
UI["Dashboard<br/>SSE 实时推送"]
|
||
AP["Admin API<br/>Provider CRUD<br/>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<br/>MemoryMax=512M<br/>LimitNOFILE=65536<br/>ProtectSystem=strict"]
|
||
end
|
||
|
||
subgraph "端口规划"
|
||
P9190[":9190 API + WebUI + Admin"]
|
||
P9191[":9191 Prometheus Metrics"]
|
||
end
|
||
|
||
subgraph "存储"
|
||
DBFILE["/opt/sidecar-v2/data/sidecar_v2.db<br/>WAL模式"]
|
||
BACKUP["/opt/sidecar-v2/backups/<br/>每日cron .backup + 7天保留"]
|
||
end
|
||
|
||
subgraph "日志"
|
||
LOG["/var/log/sidecar-v2/<br/>JSON结构化 + logrotate<br/>10MB/文件 保留30天"]
|
||
end
|
||
|
||
subgraph "防火墙"
|
||
FW["UFW: 仅192.168.1.0/24可访问 :9190/:9191"]
|
||
end
|
||
end
|
||
|
||
subgraph "可选 Docker"
|
||
DOCKER["docker run sidecar-v2<br/>-v /opt/sidecar-v2/data:/data<br/>非root用户运行<br/>HEALTHCHECK"]
|
||
end
|
||
|
||
subgraph "外部"
|
||
PROM["Prometheus :9090<br/>抓取 :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: <id> # 指定 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 评审修订版) |