forked from boos/BoosAPI
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ce11aa13a | |||
| b78b622f0c | |||
| 56b57f7ace |
@@ -0,0 +1,788 @@
|
|||||||
|
# BoosAPI Rust Backend Rewrite — 优化方案
|
||||||
|
|
||||||
|
> 基于原始计划的深度分析与重构,核心动因:**Node.js 单线程事件循环在高并发 AI API 代理场景下已触达性能天花板**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. 为什么要从 Node.js 迁移到 Rust
|
||||||
|
|
||||||
|
### 0.1 Node.js 的致命瓶颈
|
||||||
|
|
||||||
|
```
|
||||||
|
Node.js 单线程事件循环
|
||||||
|
┌──────────────────────────────────────────────┐
|
||||||
|
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
|
||||||
|
│ │ req1 │ │ req2 │ │ req3 │ │ req4 │ ... │
|
||||||
|
│ └──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘ │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ ▼ ▼ ▼ ▼ │
|
||||||
|
│ ┌────────────────────────────────────┐ │
|
||||||
|
│ │ Event Loop (1 thread) │ │
|
||||||
|
│ │ • JSON parse/serialize blocks │ │
|
||||||
|
│ │ • SSE framing blocks │ │
|
||||||
|
│ │ • Protocol transform blocks │ │
|
||||||
|
│ │ • Token routing compute blocks │ │
|
||||||
|
│ └────────────────────────────────────┘ │
|
||||||
|
└──────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
问题:1 个请求的 JSON 解析会阻塞所有其他 999 个并发请求
|
||||||
|
```
|
||||||
|
|
||||||
|
**具体场景下 Node.js 的单线程劣化**:
|
||||||
|
|
||||||
|
| 场景 | Node.js 行为 | 影响 |
|
||||||
|
|------|-------------|------|
|
||||||
|
| 1000 并发 SSE 连接 | 事件循环被打满,每帧需遍历所有连接 | P99 延迟飙升到秒级 |
|
||||||
|
| 大请求体 JSON 解析 | `JSON.parse()` 阻塞事件循环 | 所有连接暂停处理 |
|
||||||
|
| 协议转换 (Anthropic ↔ OpenAI) | CPU 密集计算占用主线程 | 代理延迟增加 50-200ms |
|
||||||
|
| Token 加权路由计算 | 计算期间无法处理新连接 | 连接建立超时 |
|
||||||
|
| 上游响应流式转发 | Buffer 拼接 + 重序列化阻塞 | SSE 流出现卡顿 |
|
||||||
|
|
||||||
|
### 0.2 Rust 的对应优势
|
||||||
|
|
||||||
|
```
|
||||||
|
Rust + tokio 多线程运行时
|
||||||
|
┌─────────────────────────────────────────────────────┐
|
||||||
|
│ tokio runtime (num_cpus threads) │
|
||||||
|
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||||
|
│ │ worker 0 │ │ worker 1 │ │ worker N │ │
|
||||||
|
│ │ ┌──────┐ │ │ ┌──────┐ │ │ ┌──────┐ │ │
|
||||||
|
│ │ │req 1 │ │ │ │req M │ │ │ │req Z │ │ │
|
||||||
|
│ │ │req 2 │ │ │ │req.. │ │ │ │req.. │ │ │
|
||||||
|
│ │ │ ... │ │ │ │ ... │ │ │ │ ... │ │ │
|
||||||
|
│ │ └──────┘ │ │ └──────┘ │ │ └──────┘ │ │
|
||||||
|
│ └──────────┘ └──────────┘ └──────────┘ │
|
||||||
|
│ │
|
||||||
|
│ + spawn_blocking for heavy CPU work │
|
||||||
|
│ + zero-cost serde (no GC pauses) │
|
||||||
|
│ + stack-allocated futures (no heap boxing) │
|
||||||
|
└─────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
优势:JSON 解析在 worker 0 阻塞时,worker 1..N 继续处理其他请求
|
||||||
|
```
|
||||||
|
|
||||||
|
| 指标 | Node.js (Fastify) | Rust (Axum) | 提升 |
|
||||||
|
|------|-------------------|-------------|------|
|
||||||
|
| 并发连接上限 | ~5k (受事件循环限制) | ~50k+ (受内存限制) | **10x** |
|
||||||
|
| JSON 反序列化 | ~80 MB/s (单线程) | ~400 MB/s (serde, 多线程) | **5x** |
|
||||||
|
| SSE 帧处理延迟 | 1-10ms (排队等待) | <100μs (独立 task) | **10-100x** |
|
||||||
|
| 内存占用/连接 | ~50KB (V8 开销) | ~5KB (栈分配) | **10x** |
|
||||||
|
| CPU 利用率 | ~1.2 核 (cluster 也不完美) | 全部 CPU 核 (tokio work stealing) | **N 核全用** |
|
||||||
|
|
||||||
|
### 0.3 结论
|
||||||
|
|
||||||
|
**Node.js cluster/worker_threads 不是答案** —— 它们在多进程间复制状态,破坏了连接亲和性,且每个 worker 仍然是单线程瓶颈。Rust + tokio 是 AI API 网关这种 **高并发 + CPU 密集混合** 场景的正确选择。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 技术栈(优化后)
|
||||||
|
|
||||||
|
| 组件 | 选择 | 变更说明 |
|
||||||
|
|------|------|---------|
|
||||||
|
| HTTP 框架 | **Axum 0.8+** | 不变 |
|
||||||
|
| 数据库 | **SQLx 0.8+** | 不变。补充:启用 `migrate` feature,使用 `journal_mode=WAL` |
|
||||||
|
| HTTP 客户端 | **reqwest** | 不变。补充:统一配置 `pool_max_idle_per_host(32)` + `timeout(Duration::from_secs(120))` |
|
||||||
|
| 认证 | **jsonwebtoken + argon2** | 不变。API Key 哈希从 Blake3 统一为 argon2id(避免维护两套哈希) |
|
||||||
|
| 序列化 | **serde + serde_json** | 不变 |
|
||||||
|
| 流式 | **axum Sse + WebSocket** | 不变 |
|
||||||
|
| 日志/追踪 | **tracing + tracing-subscriber** | **新增:opentelemetry + opentelemetry-prometheus** 导出 metrics |
|
||||||
|
| 配置 | **figment + dotenvy** | 不变 |
|
||||||
|
| CLI | **clap** | 不变 |
|
||||||
|
| 测试 | **rstest + wiremock + sqlx::test** | 不变 |
|
||||||
|
| 速率限制 | **tower::limit + governor** | ⭐ **新增** |
|
||||||
|
| 熔断器 | **tower::limit + custom circuit_breaker** | ⭐ **新增** |
|
||||||
|
| 运行时 | **tokio (multi-threaded)** | 不变 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 架构(优化后)
|
||||||
|
|
||||||
|
### 2.1 整体拓扑
|
||||||
|
|
||||||
|
```
|
||||||
|
React SPA (不变)
|
||||||
|
│
|
||||||
|
├── HTTP REST ──────┐
|
||||||
|
├── SSE ────────────┤
|
||||||
|
└── WebSocket ──────┤
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ Axum Router (tower 中间件栈) │
|
||||||
|
│ │
|
||||||
|
│ ┌─────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Middleware Stack (per-route) │ │
|
||||||
|
│ │ RateLimit → Auth → Timeout → Metrics → Handler │ │
|
||||||
|
│ └─────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────┐ ┌──────────────┐ ┌────────────────────┐ │
|
||||||
|
│ │ Routes │ │ Proxy Core │ │ Background Tasks │ │
|
||||||
|
│ │ /healthz │ │ ┌──────────┐ │ │ ┌────────────────┐ │ │
|
||||||
|
│ │ /v1/* │──│ │Conductor │ │ │ │ Cron Scheduler │ │ │
|
||||||
|
│ │ /api/* │ │ │(orchestr)│ │ │ │ 12 tokio tasks │ │ │
|
||||||
|
│ │ /v1/user │ │ └────┬─────┘ │ │ └────────────────┘ │ │
|
||||||
|
│ └──────────┘ │ │ │ └────────────────────┘ │
|
||||||
|
│ │ ▼ │ │
|
||||||
|
│ │ ┌──────────┐ │ │
|
||||||
|
│ │ │Channel │ │ │
|
||||||
|
│ │ │Selector │ │ │
|
||||||
|
│ │ │+Cooldown │ │ │
|
||||||
|
│ │ └────┬─────┘ │ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ │ ▼ │ │
|
||||||
|
│ │ ┌──────────┐ │ │
|
||||||
|
│ │ │ Retry │ │ │
|
||||||
|
│ │ │ Policy │ │ │
|
||||||
|
│ │ │+Circuit │ │ │
|
||||||
|
│ │ │ Breaker │ │ ← ⭐ 新增熔断器 │
|
||||||
|
│ │ └────┬─────┘ │ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ │ ▼ │ │
|
||||||
|
│ │ ┌──────────┐ │ │
|
||||||
|
│ │ │Transform │ │ │
|
||||||
|
│ │ │Pipeline │ │ │
|
||||||
|
│ │ └────┬─────┘ │ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ │ ▼ │ │
|
||||||
|
│ │ ┌──────────┐ │ │
|
||||||
|
│ │ │Provider │ │ │
|
||||||
|
│ │ │Executor │ │ │
|
||||||
|
│ │ └──────────┘ │ │
|
||||||
|
│ └──────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
|
||||||
|
│ │ Transformers │ │ Services │ │ DB Layer │ │
|
||||||
|
│ │ (lib crate) │ │ (biz logic) │ │ (sqlx models) │ │
|
||||||
|
│ └──────────────┘ └──────────────┘ └──────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Metrics / Health / Observability │ │
|
||||||
|
│ │ /metrics (Prometheus) /healthz (DB+upstream check) │ │
|
||||||
|
│ └──────────────────────────────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 中间件部署策略
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// 不同路由组的中间件栈不同
|
||||||
|
|
||||||
|
// /v1/* (Proxy 路由) - 最完整
|
||||||
|
// ProxyTokenAuth → RateLimit(per-token) → Timeout(120s) → Metrics → Handler
|
||||||
|
|
||||||
|
// /api/* (Admin 路由) - 管理后台
|
||||||
|
// AuthTokenAuth → RateLimit(per-user) → Timeout(30s) → Metrics → Handler
|
||||||
|
|
||||||
|
// /healthz (健康检查) - 无认证
|
||||||
|
// Timeout(5s) → Handler
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 熔断器设计(⭐ 新增)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// 每个上游 provider 独立的熔断状态
|
||||||
|
struct CircuitBreaker {
|
||||||
|
failure_count: AtomicU32,
|
||||||
|
last_failure: AtomicI64, // Unix timestamp
|
||||||
|
state: AtomicU8, // 0=Closed, 1=HalfOpen, 2=Open
|
||||||
|
threshold: u32, // 连续失败 N 次后打开
|
||||||
|
recovery_timeout: Duration, // 打开后多久进入 HalfOpen
|
||||||
|
half_open_max_requests: u32, // HalfOpen 状态允许的探测请求数
|
||||||
|
}
|
||||||
|
|
||||||
|
// 熔断状态在 proxy/conductor 中每个 provider 独立维护
|
||||||
|
// 存储于 AppState 的 Arc<RwLock<HashMap<ProviderId, CircuitBreaker>>>
|
||||||
|
```
|
||||||
|
|
||||||
|
**策略**:
|
||||||
|
- 连续 **5 次** 失败 → Open(快速失败,不发送上游请求)
|
||||||
|
- Open 持续 **30s** → HalfOpen(允许 3 个探测请求通过)
|
||||||
|
- 探测请求全部成功 → Closed;任一失败 → 重新 Open
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 目录结构(优化后)
|
||||||
|
|
||||||
|
```
|
||||||
|
boosapi/
|
||||||
|
├── Cargo.toml
|
||||||
|
├── .env.example
|
||||||
|
├── docker/
|
||||||
|
│ ├── Dockerfile # 多阶段构建
|
||||||
|
│ └── docker-compose.yml # ⭐ 新增:本地开发环境
|
||||||
|
├── migrations/
|
||||||
|
│ ├── 0001_initial_schema.sql # 核心表(~15 张)
|
||||||
|
│ ├── 0002_proxy_tables.sql # 代理相关表(~10 张)
|
||||||
|
│ ├── 0003_admin_tables.sql # 管理后台表(~7 张)
|
||||||
|
│ └── 0004_oauth_tables.sql # OAuth 表
|
||||||
|
│ # ↑ 拆分为 4 次增量迁移,每次与对应模块一起交付和测试
|
||||||
|
│ # ↓ 回滚脚本
|
||||||
|
│ └── rollback/ # ⭐ 新增:手动回滚脚本
|
||||||
|
│ ├── 0001_rollback.sql
|
||||||
|
│ ├── 0002_rollback.sql
|
||||||
|
│ ├── 0003_rollback.sql
|
||||||
|
│ └── 0004_rollback.sql
|
||||||
|
├── tests/ # 集成测试
|
||||||
|
│ ├── common/ # 测试基础设施
|
||||||
|
│ │ ├── mod.rs
|
||||||
|
│ │ ├── fixtures.rs # 录制回放 fixture
|
||||||
|
│ │ └── wiremock.rs # wiremock 辅助
|
||||||
|
│ ├── compatibility/ # ⭐ 新增:API 兼容性对比测试
|
||||||
|
│ │ ├── mod.rs
|
||||||
|
│ │ ├── snapshots/ # Node.js 录制快照
|
||||||
|
│ │ └── compare.rs # 对比 runner
|
||||||
|
│ ├── health/
|
||||||
|
│ ├── proxy/
|
||||||
|
│ ├── admin/
|
||||||
|
│ ├── transformers/
|
||||||
|
│ └── oauth/
|
||||||
|
└── src/
|
||||||
|
├── main.rs
|
||||||
|
├── lib.rs
|
||||||
|
├── config.rs
|
||||||
|
├── error.rs
|
||||||
|
├── metrics.rs # ⭐ 新增:Prometheus metrics 注册
|
||||||
|
├── routes/
|
||||||
|
│ ├── mod.rs
|
||||||
|
│ ├── health.rs
|
||||||
|
│ ├── metrics.rs # ⭐ 新增:GET /metrics
|
||||||
|
│ ├── proxy/ # /v1/*
|
||||||
|
│ ├── admin/ # /api/*
|
||||||
|
│ └── user/ # /v1/user/*
|
||||||
|
├── proxy/
|
||||||
|
│ ├── mod.rs
|
||||||
|
│ ├── conductor.rs # 请求编排
|
||||||
|
│ ├── channel_selector.rs # 加权随机 + cooldown
|
||||||
|
│ ├── retry_policy.rs
|
||||||
|
│ ├── circuit_breaker.rs # ⭐ 新增
|
||||||
|
│ ├── rate_limiter.rs # ⭐ 新增:per-token 限流
|
||||||
|
│ └── upstream.rs # 上游请求构建
|
||||||
|
├── transformers/
|
||||||
|
│ ├── mod.rs
|
||||||
|
│ ├── canonical.rs # CanonicalRequest / CanonicalResponse
|
||||||
|
│ ├── openai.rs
|
||||||
|
│ ├── anthropic.rs
|
||||||
|
│ ├── gemini.rs
|
||||||
|
│ └── shared/
|
||||||
|
│ ├── mod.rs
|
||||||
|
│ ├── think.rs # think tag 解析
|
||||||
|
│ ├── tool_names.rs
|
||||||
|
│ └── reasoning.rs
|
||||||
|
├── providers/
|
||||||
|
│ ├── mod.rs
|
||||||
|
│ ├── traits.rs # Provider trait 定义
|
||||||
|
│ ├── openai.rs
|
||||||
|
│ ├── anthropic.rs
|
||||||
|
│ ├── gemini.rs
|
||||||
|
│ ├── codex.rs
|
||||||
|
│ ├── claude.rs
|
||||||
|
│ ├── gemini_cli.rs
|
||||||
|
│ ├── antigravity.rs
|
||||||
|
│ ├── newapi.rs
|
||||||
|
│ ├── oneapi.rs
|
||||||
|
│ ├── onehub.rs
|
||||||
|
│ ├── donehub.rs
|
||||||
|
│ ├── sub2api.rs
|
||||||
|
│ └── veloera.rs
|
||||||
|
├── services/
|
||||||
|
│ ├── mod.rs
|
||||||
|
│ ├── auth.rs
|
||||||
|
│ ├── accounts.rs
|
||||||
|
│ ├── sites.rs
|
||||||
|
│ ├── token_routing.rs
|
||||||
|
│ ├── models.rs
|
||||||
|
│ ├── billing.rs
|
||||||
|
│ ├── proxy_logging.rs
|
||||||
|
│ ├── checkin.rs
|
||||||
|
│ ├── oauth.rs
|
||||||
|
│ ├── update_center.rs
|
||||||
|
│ ├── background.rs # 后台任务调度
|
||||||
|
│ ├── cron/ # ⭐ 独立 cron 子模块
|
||||||
|
│ │ ├── mod.rs
|
||||||
|
│ │ ├── model_probe.rs
|
||||||
|
│ │ ├── channel_recovery.rs
|
||||||
|
│ │ ├── usage_aggregation.rs
|
||||||
|
│ │ ├── admin_snapshot.rs
|
||||||
|
│ │ ├── oauth_refresh.rs
|
||||||
|
│ │ ├── sub2api_refresh.rs
|
||||||
|
│ │ ├── announcement_poll.rs
|
||||||
|
│ │ ├── update_poll.rs
|
||||||
|
│ │ ├── log_retention.rs
|
||||||
|
│ │ ├── file_retention.rs
|
||||||
|
│ │ └── webdav_backup.rs
|
||||||
|
│ └── notifications/ # ⭐ 独立通知子模块
|
||||||
|
│ ├── mod.rs
|
||||||
|
│ ├── telegram.rs
|
||||||
|
│ ├── wechat_work.rs
|
||||||
|
│ ├── feishu.rs
|
||||||
|
│ ├── webhook.rs
|
||||||
|
│ ├── bark.rs
|
||||||
|
│ ├── serverchan.rs
|
||||||
|
│ └── email.rs
|
||||||
|
├── db/
|
||||||
|
│ ├── mod.rs
|
||||||
|
│ ├── pool.rs
|
||||||
|
│ ├── migrate.rs
|
||||||
|
│ ├── models/
|
||||||
|
│ │ ├── mod.rs
|
||||||
|
│ │ ├── site.rs
|
||||||
|
│ │ ├── account.rs
|
||||||
|
│ │ ├── token.rs
|
||||||
|
│ │ ├── user.rs
|
||||||
|
│ │ ├── organization.rs
|
||||||
|
│ │ ├── proxy_log.rs
|
||||||
|
│ │ └── ...
|
||||||
|
│ └── queries/
|
||||||
|
│ ├── mod.rs
|
||||||
|
│ ├── site_queries.rs
|
||||||
|
│ ├── token_queries.rs
|
||||||
|
│ └── ...
|
||||||
|
├── auth/
|
||||||
|
│ ├── mod.rs
|
||||||
|
│ ├── jwt.rs
|
||||||
|
│ ├── api_key.rs
|
||||||
|
│ ├── password.rs
|
||||||
|
│ └── middleware.rs # ProxyTokenAuth, AuthTokenAuth, DownstreamApiKeyAuth
|
||||||
|
├── desktop/
|
||||||
|
│ └── mod.rs
|
||||||
|
└── shared/
|
||||||
|
├── mod.rs
|
||||||
|
├── sse.rs
|
||||||
|
├── streaming.rs
|
||||||
|
├── pagination.rs
|
||||||
|
├── time.rs
|
||||||
|
├── json.rs
|
||||||
|
└── response.rs # 统一 API 响应信封
|
||||||
|
```
|
||||||
|
|
||||||
|
**变更要点**:
|
||||||
|
- 迁移拆分为 **4 次增量**(原方案 1 次 32 张表 → 每次 4-15 张表)
|
||||||
|
- 新增 `rollback/` 回滚脚本目录
|
||||||
|
- 新增 `circuit_breaker.rs`、`rate_limiter.rs`、`metrics.rs`
|
||||||
|
- services 下的 cron 和 notifications 拆为独立子模块(原有 12+7 个文件混在一起)
|
||||||
|
- 新增 `tests/compatibility/` — API 兼容性对比测试基础设施
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 黄金规则(优化后)
|
||||||
|
|
||||||
|
1. **API 兼容性必须精确** — 端点路径、HTTP 方法、请求/响应 JSON、SSE event 格式、错误 shape 与现有 Node.js 实现完全一致。
|
||||||
|
2. **每 handler 必有测试** — `cargo test && cargo clippy -- -D warnings` 通过才能提交。
|
||||||
|
3. **一阶段一交付** — 每阶段产出 **可编译、可测试、可部署** 的产物。
|
||||||
|
4. **Phase 1 是基石** — 所有后续阶段依赖 Phase 1 的接口契约。
|
||||||
|
5. **Conventional Commits** — `feat:` / `fix:` / `refactor:` / `test:` / `docs:`。
|
||||||
|
6. **Rust 规范** — `cargo fmt` + `cargo clippy -- -D warnings`。使用 `thiserror` 和 `anyhow`。
|
||||||
|
7. **依赖克制** — `Arc<AppState>` 传递共享状态,禁止 `lazy_static`。
|
||||||
|
8. **⭐ API 兼容性验证** — 每个 proxy 路由必须有对应的录制回放快照测试,CI 中运行 `cargo test --test compatibility` 对比新旧实现行为。
|
||||||
|
9. **⭐ 性能不可退化** — P50 延迟不能超过 Node.js 实现的 80%,P99 不能超过 50%。Phase 13 中 `cargo bench` 验证。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. API 兼容性验证框架(⭐ 新增)
|
||||||
|
|
||||||
|
### 5.1 录制回放策略
|
||||||
|
|
||||||
|
```
|
||||||
|
Phase 0 (预备): 在 Node.js 后端前挂代理录制
|
||||||
|
┌──────────┐ ┌──────────────┐ ┌──────────────┐
|
||||||
|
│ Test │────▶│ Recorder │────▶│ Node.js │
|
||||||
|
│ Client │ │ (mitmproxy) │ │ Backend │
|
||||||
|
└──────────┘ └──────┬───────┘ └──────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌──────────────┐
|
||||||
|
│ Snapshots/ │
|
||||||
|
│ *.json │
|
||||||
|
└──────────────┘
|
||||||
|
|
||||||
|
Phase 1-13 (实现): 每次 CI 运行对比测试
|
||||||
|
┌──────────┐ ┌──────────────┐
|
||||||
|
│ Cargo │────▶│ Compare │
|
||||||
|
│ Test │ │ Runner │
|
||||||
|
└──────────┘ └──────┬───────┘
|
||||||
|
│
|
||||||
|
┌────────────────┼────────────────┐
|
||||||
|
▼ ▼ ▼
|
||||||
|
┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||||
|
│ Snapshots│ │ Response │ │ Diff │
|
||||||
|
│ (record) │ │ (actual) │ │ Report │
|
||||||
|
└──────────┘ └──────────┘ └──────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 快照格式
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "chat_completions_basic",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"path": "/v1/chat/completions",
|
||||||
|
"headers": { "authorization": "Bearer sk-***", "content-type": "application/json" },
|
||||||
|
"body": { "model": "gpt-4o", "messages": [{"role": "user", "content": "hi"}] }
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"status": 200,
|
||||||
|
"headers": { "content-type": "application/json" },
|
||||||
|
"body": { "id": "*", "object": "chat.completion", "choices": [...] },
|
||||||
|
"ignore_fields": ["id", "created"] // 这些字段可以不同
|
||||||
|
},
|
||||||
|
"sse_events": null // 非流式请求
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 CI 中的兼容性门禁
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# .github/workflows/compat.yml (或对应的 CI 配置)
|
||||||
|
cargo test --test compatibility -- --snapshot-dir tests/compatibility/snapshots
|
||||||
|
# 任何字段不匹配 → CI 失败
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 分阶段计划(重构后)
|
||||||
|
|
||||||
|
### Phase 0: 兼容性快照录制 (1-2 天) ⭐ 新增
|
||||||
|
|
||||||
|
**目标**:在重构开始前录制 Node.js 实现的完整请求/响应对。
|
||||||
|
|
||||||
|
- 搭建 mitmproxy 录制环境指向现有 Node.js 后端
|
||||||
|
- 对每个 proxy 端点录制 3-5 个典型场景(正常、异常、边界)
|
||||||
|
- 快照存入 `tests/compatibility/snapshots/`
|
||||||
|
- **无此快照,后续所有 "API 兼容" 承诺都不可验证**
|
||||||
|
|
||||||
|
### Phase 1: 项目脚手架 + 基础设施 (3-5 天)
|
||||||
|
|
||||||
|
与原始计划基本一致,增补:
|
||||||
|
|
||||||
|
- [x] `cargo init` + 完整 `Cargo.toml`
|
||||||
|
- [x] Figment 配置加载(`.env` + 环境变量 + TOML 文件三层覆盖)
|
||||||
|
- [x] `AppError` 统一错误类型(`thiserror`)
|
||||||
|
- [x] `AppState` 定义(含 `Arc<CircuitBreakerRegistry>`、`Arc<RateLimiterRegistry>`)
|
||||||
|
- [x] 数据库连接池 + 迁移运行
|
||||||
|
- [x] `main.rs` graceful shutdown(`tokio::signal`)
|
||||||
|
- [x] `GET /healthz` — 返回 DB 连通性 + 上游 provider 状态
|
||||||
|
- [x] `GET /metrics` — Prometheus 格式 metrics 端点
|
||||||
|
- [x] **迁移 0001**:核心表(sites, accounts, users, tokens, organizations)
|
||||||
|
- [x] 多阶段 Dockerfile + docker-compose.yml
|
||||||
|
- [x] CI 配置文件(GitHub Actions / Gitea Actions)
|
||||||
|
- [x] `cargo build && cargo test && cargo clippy -- -D warnings` 全部通过
|
||||||
|
|
||||||
|
### Phase 2: 共享类型 & 工具 (2-3 天)
|
||||||
|
|
||||||
|
- SSE event 辅助类型
|
||||||
|
- 分页参数 + 响应信封
|
||||||
|
- 统一 API 响应格式 `ApiResponse<T>`
|
||||||
|
- **兼容性测试框架**(对比 runner + snapshot loader)
|
||||||
|
|
||||||
|
### Phase 3: 认证 (3-4 天)
|
||||||
|
|
||||||
|
- JWT 生成/校验
|
||||||
|
- API Key 哈希/校验(**统一使用 argon2id**,不再用 Blake3)
|
||||||
|
- 密码哈希(argon2id)
|
||||||
|
- 三个中间件:`ProxyTokenAuth`、`AuthTokenAuth`、`DownstreamApiKeyAuth`
|
||||||
|
- 中间件测试覆盖率 ≥90%
|
||||||
|
|
||||||
|
### Phase 4-7: 核心业务(可部分并行,~4-6 周)
|
||||||
|
|
||||||
|
详见第 7 节的并行策略。
|
||||||
|
|
||||||
|
真实依赖关系(修正原方案中过于乐观的并行假设):
|
||||||
|
|
||||||
|
```
|
||||||
|
Phase 4 (Admin API) ──────── 依赖 Phase 1,2,3 ──── 独立
|
||||||
|
Phase 5 (Proxy Core) ─────── 依赖 Phase 1,2,3 ──── 独立
|
||||||
|
Phase 6 (Transformers) ───── 依赖 Phase 5 trait ─ 需要 Phase 5 的 trait 定义
|
||||||
|
Phase 7 (Providers) ──────── 依赖 Phase 6 ──────── 需要 CanonicalFormat
|
||||||
|
```
|
||||||
|
|
||||||
|
**修正**:Phase 5 先产出 trait 定义 → Phase 6 和 Phase 7 同时基于 trait 开发。
|
||||||
|
|
||||||
|
### Phase 8: 流式 (SSE + WebSocket) (3-5 天)
|
||||||
|
|
||||||
|
- SSE 流式中继(带背压处理)
|
||||||
|
- WebSocket handler
|
||||||
|
- 流式 transformer pipeline
|
||||||
|
- **关键**:背压策略 —— 当上游快于下游时,使用 `tokio::sync::mpsc::channel` 有界通道,channel 满时暂停上游读取
|
||||||
|
|
||||||
|
### Phase 9: 后台服务 (3-5 天)
|
||||||
|
|
||||||
|
- 每个 cron 任务独立的 `tokio::task` + `tokio::time::interval`
|
||||||
|
- 优雅关闭:所有后台任务监听 `CancellationToken`
|
||||||
|
- 熔断器状态持久化(可选:重启后从 DB 恢复 provider 熔断状态)
|
||||||
|
|
||||||
|
### Phase 10-12: 边缘特性 (~2-3 周)
|
||||||
|
|
||||||
|
- OAuth flow + 通知 + ComfyUI 反向代理 + Video Agent
|
||||||
|
- 这些不阻塞主流程,可以后置
|
||||||
|
|
||||||
|
### Phase 13: 集成测试 & 性能基准 (1-2 周)
|
||||||
|
|
||||||
|
除了原计划内容,新增:
|
||||||
|
- 所有兼容性快照测试通过
|
||||||
|
- `cargo bench` 性能基准(criterion)
|
||||||
|
- 压测对比报告(Node.js vs Rust,同机器同配置)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 并行工作流(修正后)
|
||||||
|
|
||||||
|
### 7.1 修正的依赖图
|
||||||
|
|
||||||
|
```
|
||||||
|
Phase 1 (Scaffold)
|
||||||
|
│
|
||||||
|
├──▶ Phase 2 (Shared Types)
|
||||||
|
│ │
|
||||||
|
│ ▼
|
||||||
|
│ Phase 3 (Auth)
|
||||||
|
│ │
|
||||||
|
│ ┌────┴────────────────────┐
|
||||||
|
│ │ │
|
||||||
|
│ ▼ ▼
|
||||||
|
│ Phase 5 (Proxy) Phase 4 (Admin)
|
||||||
|
│ │ trait 定义 │ ← 真正独立
|
||||||
|
│ │ │
|
||||||
|
│ ├──▶ Phase 6 (Transformer) ← 基于 trait
|
||||||
|
│ │ │
|
||||||
|
│ │ ▼
|
||||||
|
│ │ Phase 7 (Providers) ← 依赖 CanonicalFormat
|
||||||
|
│ │ │
|
||||||
|
│ └────────┤
|
||||||
|
│ ▼
|
||||||
|
│ Phase 8 (SSE/WS) ← 依赖 5+6+7
|
||||||
|
│
|
||||||
|
├──▶ Phase 9 (Background) ← 依赖 Phase 4 的 services
|
||||||
|
│
|
||||||
|
├──▶ Phase 10 (OAuth) ← 依赖 Phase 4
|
||||||
|
│
|
||||||
|
├──▶ Phase 11 (Notifications) ← 真正独立
|
||||||
|
│
|
||||||
|
├──▶ Phase 12 (Edge Features) ← 真正独立
|
||||||
|
│
|
||||||
|
└──▶ Phase 13 (Final QA) ← 需要所有前置
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 修正后的工作流分配
|
||||||
|
|
||||||
|
| 工作流 | 负责 | 真实并行度 | 备注 |
|
||||||
|
|--------|------|-----------|------|
|
||||||
|
| A: Core | Phase 1,2,3,5 | 阻塞 C,D | 必须先产出 Proxy trait |
|
||||||
|
| B: Admin | Phase 4 | **与 A 并行** | 真正的独立流 |
|
||||||
|
| C: Transformer | Phase 6 | **等待 A 的 trait** | 不能和 A 同时启动 |
|
||||||
|
| D: Provider | Phase 7 | **等待 C** | 不能和 C 同时启动 |
|
||||||
|
| E: Platform | Phase 9,10,11 | **等待 B 部分完成** | 假并行 |
|
||||||
|
| F: Edge | Phase 12 | **与所有并行** | 真正的独立流 |
|
||||||
|
| G: QA | Phase 13 + 持续测试 | **尾随** | 每 Phase 合入时跑测试 |
|
||||||
|
|
||||||
|
### 7.3 基于实际约束的执行时间线
|
||||||
|
|
||||||
|
```
|
||||||
|
Week 1-2: Phase 0 (快照) + Phase 1 (脚手架)
|
||||||
|
Week 3: Phase 2 (共享类型) + Phase 3 (Auth)
|
||||||
|
Week 4-5: Phase 4 (Admin) || Phase 5 (Proxy trait + 基础实现)
|
||||||
|
Week 6-7: Phase 6 (Transformers) || Phase 4 收尾
|
||||||
|
Week 7-8: Phase 7 (Providers)
|
||||||
|
Week 9: Phase 8 (SSE/WS)
|
||||||
|
Week 10: Phase 9 (Background) || Phase 10 (OAuth)
|
||||||
|
Week 11: Phase 11 (Notifications) || Phase 12 (Edge)
|
||||||
|
Week 12-13: Phase 13 (QA, compat tests, bench)
|
||||||
|
Week 14: Buffer / 灰度发布
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Provider 实现优先级(不变)
|
||||||
|
|
||||||
|
| 优先级 | Provider |
|
||||||
|
|--------|----------|
|
||||||
|
| P0 | OpenAI (Chat/Models/Images/Embeddings/Audio/Files), Anthropic (Messages) |
|
||||||
|
| P1 | Gemini, NewAPI/OneAPI, Codex/Claude |
|
||||||
|
| P2 | Sub2API, Veloera/DoneHub/OneHub, Antigravity |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 关键代码契约(供并行开发使用)
|
||||||
|
|
||||||
|
### 9.1 Provider trait(Phase 5 产出,Phase 7 实现)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// src/providers/traits.rs
|
||||||
|
/// Phase 5 必须产出此 trait 并冻结签名
|
||||||
|
#[async_trait]
|
||||||
|
pub trait Provider: Send + Sync + 'static {
|
||||||
|
fn provider_id(&self) -> ProviderId;
|
||||||
|
|
||||||
|
async fn chat_completion(
|
||||||
|
&self,
|
||||||
|
request: CanonicalRequest,
|
||||||
|
channel: &Channel,
|
||||||
|
) -> Result<CanonicalResponse, ProviderError>;
|
||||||
|
|
||||||
|
async fn chat_completion_stream(
|
||||||
|
&self,
|
||||||
|
request: CanonicalRequest,
|
||||||
|
channel: &Channel,
|
||||||
|
) -> Result<StreamRelay, ProviderError>;
|
||||||
|
|
||||||
|
fn supported_models(&self) -> &'static [&'static str];
|
||||||
|
|
||||||
|
/// 模型能力声明
|
||||||
|
fn capabilities(&self) -> ModelCapabilities {
|
||||||
|
ModelCapabilities::default() // 各 provider 按需覆盖
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 Error 扩展模式
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// src/error.rs
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum AppError {
|
||||||
|
#[error("Auth: {0}")]
|
||||||
|
Auth(#[from] AuthError),
|
||||||
|
|
||||||
|
#[error("Proxy: {0}")]
|
||||||
|
Proxy(#[from] ProxyError),
|
||||||
|
|
||||||
|
#[error("Upstream provider: {0}")]
|
||||||
|
Provider(#[from] ProviderError),
|
||||||
|
|
||||||
|
#[error("Transform: {0}")]
|
||||||
|
Transform(#[from] TransformError),
|
||||||
|
|
||||||
|
#[error("Circuit breaker open for provider {0}")]
|
||||||
|
CircuitBreakerOpen(String),
|
||||||
|
|
||||||
|
#[error("Rate limit exceeded for {0}")]
|
||||||
|
RateLimited(String),
|
||||||
|
|
||||||
|
#[error("Database: {0}")]
|
||||||
|
Database(#[from] sqlx::Error),
|
||||||
|
|
||||||
|
#[error("Internal: {0}")]
|
||||||
|
Internal(#[from] anyhow::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl axum::response::IntoResponse for AppError {
|
||||||
|
fn into_response(self) -> axum::response::Response {
|
||||||
|
// 统一错误 shape,与 Node.js 实现一致:
|
||||||
|
// { "error": { "message": "...", "type": "...", "code": "..." } }
|
||||||
|
let (status, code, message) = match &self {
|
||||||
|
AppError::Auth(_) => (StatusCode::UNAUTHORIZED, "auth_error", self.to_string()),
|
||||||
|
AppError::CircuitBreakerOpen(_) => (StatusCode::SERVICE_UNAVAILABLE, "circuit_breaker_open", self.to_string()),
|
||||||
|
AppError::RateLimited(_) => (StatusCode::TOO_MANY_REQUESTS, "rate_limited", self.to_string()),
|
||||||
|
_ => (StatusCode::INTERNAL_SERVER_ERROR, "internal_error", self.to_string()),
|
||||||
|
};
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 可观测性(⭐ 新增)
|
||||||
|
|
||||||
|
### 10.1 Prometheus Metrics
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// src/metrics.rs
|
||||||
|
use prometheus::{HistogramVec, CounterVec, IntGaugeVec};
|
||||||
|
|
||||||
|
pub struct Metrics {
|
||||||
|
/// HTTP 请求延迟直方图 (method, path, status)
|
||||||
|
pub http_request_duration: HistogramVec,
|
||||||
|
|
||||||
|
/// 上游 provider 调用延迟 (provider_id, model, operation)
|
||||||
|
pub upstream_duration: HistogramVec,
|
||||||
|
|
||||||
|
/// 上游 provider 错误计数 (provider_id, error_type)
|
||||||
|
pub upstream_errors: CounterVec,
|
||||||
|
|
||||||
|
/// 当前活跃 SSE 连接数
|
||||||
|
pub active_sse_connections: IntGauge,
|
||||||
|
|
||||||
|
/// 当前活跃 WebSocket 连接数
|
||||||
|
pub active_ws_connections: IntGauge,
|
||||||
|
|
||||||
|
/// 熔断器状态 (provider_id) — 0=Closed, 1=HalfOpen, 2=Open
|
||||||
|
pub circuit_breaker_state: IntGaugeVec,
|
||||||
|
|
||||||
|
/// 速率限制触发计数 (route, token_id)
|
||||||
|
pub rate_limit_hits: CounterVec,
|
||||||
|
|
||||||
|
/// 连接池使用率
|
||||||
|
pub db_pool_connections: IntGauge,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.2 健康检查
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// GET /healthz 返回
|
||||||
|
{
|
||||||
|
"status": "ok", // "ok" | "degraded" | "down"
|
||||||
|
"db": "connected", // "connected" | "error"
|
||||||
|
"upstreams": {
|
||||||
|
"openai": "healthy",
|
||||||
|
"anthropic": "healthy",
|
||||||
|
"gemini": "degraded" // 延迟过高但可用
|
||||||
|
},
|
||||||
|
"uptime_seconds": 123456
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 风险矩阵
|
||||||
|
|
||||||
|
| 风险 | 概率 | 影响 | 缓解措施 |
|
||||||
|
|------|------|------|---------|
|
||||||
|
| API 兼容性遗漏 | 高 | 严重 | Phase 0 录制快照 + Phase 13 全量对比 |
|
||||||
|
| 迁移一次性 32 表失败 | 中 | 高 | 拆分为 4 次增量迁移 |
|
||||||
|
| Provider 协议变化 | 高 | 中 | wiremock 隔离上游,ci 快速发现 |
|
||||||
|
| 人员并行冲突 | 中 | 中 | 文件所有权矩阵 + 串行合入 |
|
||||||
|
| 性能劣化(P99 > Node.js) | 低 | 严重 | Phase 13 压测对比,提前 gate |
|
||||||
|
| SSE 背压内存泄漏 | 中 | 高 | 有界 channel + 连接数上限 + metrics 告警 |
|
||||||
|
| SQLx 编译慢(32 表) | 中 | 低 | 增量迁移 + CI 缓存 target/ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. 灰度发布策略(⭐ 新增)
|
||||||
|
|
||||||
|
```
|
||||||
|
Phase 13 完成后:
|
||||||
|
|
||||||
|
Step 1: 10% 流量 → Rust,90% → Node.js(持续 3 天)
|
||||||
|
监控: P50/P99 延迟、错误率、内存使用
|
||||||
|
|
||||||
|
Step 2: 50% 流量 → Rust,50% → Node.js(持续 3 天)
|
||||||
|
对比: 两个版本的同指标差异
|
||||||
|
|
||||||
|
Step 3: 100% 流量 → Rust,Node.js 作为热备(持续 1 周)
|
||||||
|
确认稳定后下线 Node.js 实例
|
||||||
|
|
||||||
|
Step 4: Node.js 实例保留 1 个月(冷备),之后销毁
|
||||||
|
|
||||||
|
回滚条件:
|
||||||
|
- 错误率 > Node.js 版本的 2x
|
||||||
|
- P99 延迟 > Node.js 版本的 120%
|
||||||
|
- 内存持续增长(疑似泄漏)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 附录: 原始方案主要变更总结
|
||||||
|
|
||||||
|
| 变更项 | 原始方案 | 优化方案 |
|
||||||
|
|--------|---------|---------|
|
||||||
|
| 迁移策略 | 1 次 32 张表 | 4 次增量迁移 + 回滚脚本 |
|
||||||
|
| API 兼容验证 | "exact compatibility" 无保障机制 | Phase 0 录制 + CI 对比测试 |
|
||||||
|
| 熔断器 | 无 | per-provider 独立熔断 |
|
||||||
|
| 速率限制 | 无 | tower::limit + governor |
|
||||||
|
| 可观测性 | tracing 日志 | + Prometheus metrics + /healthz 深度检查 |
|
||||||
|
| 并行策略 | 7 流同时启动 | 按真实依赖分阶段启动 |
|
||||||
|
| Provider trait | 未定义 | 锁定 trait 签名作为契约 |
|
||||||
|
| 灰度发布 | 无 | 4 步渐进切流 |
|
||||||
|
| API Key 哈希 | Blake3 + Argon2 两套 | 统一为 argon2id |
|
||||||
|
| 时间估算 | 未给出 | ~14 周到生产 |
|
||||||
Reference in New Issue
Block a user