Skip to content

系统架构

Sub2API 采用模块化单体架构:前端、HTTP API、网关协议适配、调度、计费和后台任务由同一个 Go 服务交付,PostgreSQL 和 Redis 是外部依赖。

text
                         ┌────────────────────────────┐
用户 / Claude Code ─────▶│                            │
Codex / Gemini CLI ─────▶│       Sub2API 服务         │──────▶ AI 上游平台
Web 管理后台 ────────────▶│ Gin + Handler + Service    │
                         │                            │
                         └──────┬──────────────┬──────┘
                                │              │
                         ┌──────▼─────┐  ┌─────▼──────┐
                         │ PostgreSQL │  │   Redis    │
                         │ 持久化数据  │  │ 协调与缓存 │
                         └────────────┘  └────────────┘

应用服务

HTTP 与路由层

Gin 同时注册三类路由:

  • /api/v1/*:登录、用户、管理、支付和设置 API。
  • /v1/*:Anthropic/OpenAI 风格的统一网关入口。
  • /v1beta/*:Gemini 原生兼容入口。

路由会先应用请求体大小、Request ID、错误采样、API Key 鉴权和分组检查,再交给协议 Handler。

Handler 层

Handler 负责处理与 HTTP 协议紧密相关的工作:

  • 读取和校验请求体。
  • 判断客户端、模型、流式模式和目标平台。
  • 获取用户和账号并发槽位。
  • 组织账号故障切换循环。
  • 输出 JSON、SSE 或 WebSocket 响应。

Service 层

Service 承担主要业务规则:

  • 账号候选过滤与调度评分。
  • 模型映射和多协议转换。
  • OAuth token 刷新和上游请求构建。
  • 计价、扣费、订阅和配额判断。
  • 支付、内容审核、渠道监控和运维聚合。

Repository 层

Repository 将业务对象映射到 PostgreSQL 与 Redis:

  • Ent 负责核心关系数据。
  • 手写 SQL 处理复杂统计、批处理和性能敏感路径。
  • Redis Lua 脚本保证并发槽位、限流和部分计数操作的原子性。

PostgreSQL 保存什么

  • 用户、身份绑定和 TOTP 状态。
  • API Key、账号、分组和渠道配置。
  • 订阅、订单、支付审计、兑换码和推广记录。
  • 请求用量、计费去重、平台配额和统计聚合。
  • 监控历史、错误日志、告警和后台任务状态。

数据库迁移在服务启动过程中按文件名顺序执行,并记录文件名与校验和。迁移是向前执行的,正式升级前必须备份数据库。

Redis 保存什么

  • 用户、API Key 和账号并发槽位。
  • RPM、Token、窗口用量和临时限流状态。
  • API Key 鉴权、订阅和余额缓存。
  • 粘性会话与账号绑定。
  • OAuth 刷新锁和后台任务 leader lock。
  • 批量图片任务队列与临时状态。

Redis 不是“可有可无的页面缓存”。即使 PostgreSQL 数据完整,Redis 故障也会影响调度、限流、并发和部分后台任务。

多实例运行

Sub2API 的请求处理可以运行多个副本,Redis 负责跨实例并发和锁协调。周期任务通常通过 Redis leader lock 选出单个执行者,部分任务在 Redis 异常时使用 PostgreSQL advisory lock 兜底。

多实例部署时要特别注意:

  1. 所有实例必须连接同一个 PostgreSQL 和 Redis。
  2. JWT_SECRETTOTP_ENCRYPTION_KEY、时区和关键运行配置必须一致。
  3. 数据库总连接数约等于“实例数 × 每实例连接池上限”。
  4. 反向代理需要支持长连接、SSE、WebSocket 和真实客户端 IP。
  5. 不要让某个实例使用本地独有的动态配置文件。

单二进制交付

正式发布时,Vite 构建产物会通过 Go embed 打进服务二进制。浏览器页面与 API 使用相同域名,避免额外维护独立前端服务和跨域配置。

一次请求跨越哪些层

text
server/routes
    │ 注册 URL 与中间件

handler
    │ HTTP、协议、并发等待、failover 循环

service
    │ 账号调度、协议转换、上游访问、计费规则

repository
    │ PostgreSQL、Redis、Lua、复杂 SQL

external systems
      AI 上游 / 支付平台 / SMTP / 对象存储

这个分层不是绝对隔离。网关 Handler 为了处理流式输出和故障切换,会持有多个 Service;部分 Service 为性能直接使用缓存接口或批量 SQL。因此理解真实调用链比只看目录名更重要。

启动过程

应用启动大致经历:

  1. 解析 --version--setup 等命令行参数。
  2. 判断是否首次安装。
  3. 首次安装时启动设置服务,或通过 AUTO_SETUP 自动初始化。
  4. 加载 YAML、环境变量和默认配置。
  5. 初始化日志、PostgreSQL、Ent 与 Redis。
  6. 通过 Wire 构造 Repository、Service、Handler 和 HTTP Server。
  7. 启动 token 刷新、过期检查、聚合、清理、监控等后台任务。
  8. 注册健康检查、管理 API、用户 API、支付回调和网关路由。
  9. 开始监听端口,并等待 SIGINT/SIGTERM 优雅退出。

优雅退出时,应用会先停止后台任务和 worker pool,再关闭 Redis 与 Ent。容器编排的终止宽限时间不应短于应用自身的清理时间。

依赖注入

后端使用 Google Wire 在编译期生成依赖装配代码。主要 ProviderSet 包括:

  • config.ProviderSet
  • repository.ProviderSet
  • service.ProviderSet
  • payment.ProviderSet
  • middleware.ProviderSet
  • handler.ProviderSet
  • server.ProviderSet

它的优点是依赖关系在编译期检查,没有运行时容器反射;代价是新增 Service 构造参数后需要重新生成 Wire,并同步更新大量测试 stub。

数据路径与控制路径

数据路径

模型请求属于高频数据路径:

text
鉴权缓存 → 配额检查 → Redis 并发 → 调度快照 → 上游连接池
       → SSE/WS 转发 → 异步用量记录 → 余额/订阅更新

它追求低延迟、少读数据库和可控的内存占用,因此大量使用 L1/L2 缓存、Redis Lua、调度快照和有界 worker pool。

控制路径

后台配置属于低频控制路径:

text
管理员修改账号/分组/设置 → PostgreSQL 事务
       → 缓存失效/版本递增 → 调度快照或运行时配置刷新

控制路径写入成功但缓存未及时刷新,会造成短时间旧配置;因此项目包含缓存版本、outbox、快照重建和受控数据库回源机制。

一致性策略

Sub2API 不是所有操作都使用同一种一致性模型:

数据典型策略原因
用户、订单、订阅PostgreSQL 事务需要持久化和约束
并发槽位Redis Lua 原子操作高频、跨实例、带 TTL
API Key 鉴权进程 L1 + Redis L2 + DB降低热路径查询
调度候选Redis 快照 + 受控 DB 回源兼顾性能与恢复能力
粘性会话Redis TTL临时关系,无需永久保存
用量记录有界异步队列,默认溢出同步执行避免请求路径无界 goroutine,同时尽量不丢计费
仪表盘预聚合表 + 缓存避免扫描完整 usage_logs

部署者需要接受:部分管理修改和统计展示是最终一致,但余额、支付和订阅等财务状态必须以 PostgreSQL 事务结果为准。

水平扩展时的共享与本地状态

必须共享

  • PostgreSQL 数据库。
  • Redis 数据库。
  • JWT 与 TOTP 密钥。
  • 平台 OAuth 客户端配置。
  • 对外 URL、时区和计费配置。

每实例本地状态

  • 进程内 L1 缓存。
  • HTTP 与 WebSocket 连接池。
  • 日志文件。
  • 部分 worker pool 和运行时指标。
  • 本地挂载的自定义模板或静态覆盖文件。

多实例时,本地文件要么在所有副本保持一致,要么改用共享存储。不要让请求随机落到配置不同的副本。

主要故障域

故障直接影响应对重点
PostgreSQL 不可用登录、配置、用量持久化、订单数据库 HA、连接池、备份恢复
Redis 不可用鉴权缓存、并发、调度、锁Redis HA、超时、连接池、降级观察
上游账号失效部分模型请求失败token 刷新、账号测试、failover
代理故障绑定账号全部不可用代理监控、禁止静默直连
应用实例退出该实例连接中断多副本、优雅退出、负载均衡
用量队列拥塞结算延迟或同步回退监控队列、worker、数据库写延迟
配置错误大范围无可用账号或错误计费变更审计、灰度、快速回滚

容量瓶颈通常在哪里

  1. 上游账号并发与额度:先于应用 CPU 成为瓶颈。
  2. Redis 延迟:每个请求会执行多次并发、限流和缓存操作。
  3. PostgreSQL 用量写入:高 QPS 下 usage_logs、聚合和索引维护压力上升。
  4. 长连接数量:SSE/WS 会长期占用文件描述符、内存和连接池。
  5. 管理查询:大时间范围统计可能扫描大量历史数据。

因此扩容不能只增加应用副本。还要同步检查 Redis、数据库连接数、上游账号池和反向代理文件描述符。

继续深入

本文档用于帮助你理解、部署和运维 Sub2API。使用前请确认上游服务条款与当地法律要求。