Files
EnterpriseArchitect/docs/architecture/ADR-006-sidecar-v2-architecture.md
T
vincent 0894a86af8 ADR-006 v2.1: final revision, NVIDIA provider keys, reply to 徐聪
v2.1 changes from 2nd-round review:
+ Emergency channel RPM: max(1, max_rpm * 0.1)
+ Queue 503: add Retry-After: 30 header
+ sidecar_backup_success Prometheus metric
+ Startup crypto.py key validation on boot
+ SQLite size limits: 100MB practical, 500MB WAL
+ RPM flow: per-request counting, not token-based
+ SSE streaming: TTFT for avg_latency_ms
+ Merge proxy/retry.py into core/cooldown.py

Added sidecar-v2-nvidia-providers.yaml (11 keys)

Co-authored-by: multica-agent <github@multica.ai>
2026-06-25 15:19:21 +08:00

42 KiB
Raw Blame History

ADR-006: Sidecar V2 — Provider 池管理与负载均衡架构设计

属性
编号 ADR-006
日期 2026-06-25
版本 v2.1(第二轮评审修订版)
状态 提议中
作者 梁思筑 (architect)
关联 BIZ-48 (父需求), BIZ-51 (本任务)
评审 陆怀瑾(COO), 沈路明(PM), 严维序(Ops), 徐聪(Dev)
类型 系统架构设计

1. 背景与目标

1.1 现状分析(Sidecar V1

Sidecar V1 是一个单 ProviderNVIDIA)的限流代理,核心架构:

请求 → 优先级队列 → 令牌桶限流(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 系统拓扑图

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 数据流设计

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 部署架构

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: SQLiteWAL 模式) 单文件,零配置,事务安全 选用
B: MySQL 8.0 团队熟悉,但增加运维 备选(Provider > 50 时迁移)

SQLite 运维配置

  • WAL 模式:PRAGMA journal_mode=WAL
  • WAL 检查点:wal_autocheckpoint=10001000 页后自动 checkpoint
  • 定时 checkpointcron 每小时 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,以 max(1, max_rpm * 0.1) RPM 继续尝试(底线至少 1 RPM),避免完全断流。

冷却预检:降级到 Fallback 池前,检查主池是否有 Provider 剩余冷却时间 < 10s,可短暂等待其恢复。

3.6 队列控流机制

参数 配置 说明
队列模型 FIFO + 优先级 继承 V1 的四级优先级(URGENT > HIGH > NORMAL > LOW
最大队列深度 500 超出后触发溢出策略
令牌等待超时 30s(默认) 可配置 SIDECAR_QUEUE_TIMEOUT
溢出策略 REJECT (503) 队列满时拒绝新请求,返回 503 + Retry-After: 30 header

溢出策略选择

  • 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.jsV1 风格) 轻量,无需构建 选用
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 图

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

-- 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 代理 APIV1 兼容)

方法 路径 说明
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

# 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

请求:

{
  "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
}

响应(成功):

{
  "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 无效):

{
  "error": "api_key_invalid",
  "message": "API Key 验证失败:上游返回 401 Unauthorized"
}

GET /api/v2/pools

响应:

{
  "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(默认)

防火墙规则:

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 备份策略

# /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)

| sidecar_backup_success | Gauge | 最近一次备份是否成功 (0/1),连续 3 天失败触发告警 |

系统级监控:

  • 磁盘使用率:> 80% 告警,> 90% 严重告警
  • IO 延迟:> 100ms 告警
  • SQLite 文件大小经验上限:~100MB(实用),~500MBWAL 上限),超出则触发 MySQL 迁移评估

11.5 启动时加密密钥校验

启动时 crypto.py 初始化必须执行以下校验:

  1. 检查 SIDECAR_ENCRYPTION_KEY 环境变量是否存在 → 不存在则启动失败,输出 FATAL: SIDECAR_ENCRYPTION_KEY not set
  2. 验证密钥长度 = 64 字符(32 字节 hex)→ 不符合则启动失败,输出 FATAL: SIDECAR_ENCRYPTION_KEY must be 64 hex chars (32 bytes)
  3. 尝试解密 providers 表中已有的 api_key 字段 → 解密失败则输出 WARNING 日志(密钥可能已更换,已有 Key 不可恢复)

11.6 配置热加载

  • 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 出现问题:

# 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.0 徐聪三项技术答疑

Q1: RPM 流控是请求数还是 Token 数?

A: 请求数流控。每个 HTTP 请求消耗 1 个 RPM 令牌。Token 用量仅用于事后统计(provider_usage_logs)和费用计算(cost 字段)。rate_limiter.acquire(estimated_tokens=0)——estimated_tokens 参数保留但在 V2 中固定为 0,为未来 TPM 流控预留接口。

Q2: SSE 流式响应的用量统计?

A: 流式响应在流结束后写入一条 provider_usage_logs 记录:

  • total_tokens:从最后一个 usage chunk 或累加 choices[].delta.content 长度估算
  • avg_latency_ms:记录 TTFTTime To First Token);非流式记录端到端延迟
  • request_count:流式也计为 1 次请求

Q3: proxy/retry.pycore/cooldown.py 的关系?

A: 建议采纳。将重试逻辑合并到 core/cooldown.py,对外暴露 should_retry()select_fallback() 接口。简化后的模块结构如下。

15. 核心模块设计

15.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         # 请求代理处理器(重试/降级调用 cooldown.py
│
├── 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

15.2 并发控制关键代码模式

Provider 选择 + RPM 预占必须原子化:

# 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 避免竞态):

# 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)
        """), {...})

16. 非功能需求

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 可观测性

维度 工具
结构化日志 structlogJSON 格式)
实时指标 Prometheus(独立 :9191 端口,13 项指标)
Dashboard SSE 实时推送 + Chart.js5 个面板)
健康检查 /health + /health/ready + /status

17. 开发排期(最终版 v2.1

新增 P0:文档推送 + 环境准备(3h)

  • 推送架构文档 + 图表到 Git
  • 确认开发环境(Python 3.12 + 依赖)

Phase 1: 数据层 + Provider CRUD12h

  • SQLite 数据库 + SQLAlchemy ORM + 6 张表 DDL
  • AES-256-GCM 加密模块(密钥从环境变量注入)
  • Provider CRUD API(添加含 test call 验证)
  • 并发压测(100 QPS+ 验证 SQLite WAL

Phase 2: 池管理 + 负载均衡(14h)

  • PoolManagermain / 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 + Dashboard10h

  • 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 天)

18. 协作节点

阶段 协作 Agent 事项
P0 完成 严维序 审查部署拓扑,确认运维可行性
P2 完成 严维序 Prometheus metrics 接入 + 告警规则
P5 阶段 沈路明 按 PRD 验收标准做功能验收
P6 完成 严维序 灰度部署 + 监控确认

19. 技术决策摘要

决策 选择 关键理由
数据库 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

20. 风险评估

风险 概率 影响 应对措施
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 天

21. 下游交付说明

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_TOKENAdmin Token
  • SQLite 备份:cron 每日 03:00,保留 7 天
  • 日志轮转:10MB/文件,保留 30 天
  • 防火墙:仅 192.168.1.0/24 可访问
  • 部署方式:systemd 主方案,Docker 备选

架构格言:好的架构就像城市的下水道——平时看不见,但一旦出问题就是灾难。 — 梁思筑,2026-06-25 (v2.1 第二轮评审修订版)