Files
hair/docs/系统架构-网关与高性能后端.md
T
xsl 2e789d4efa docs: 架构拆分为外网网关+高性能worker(GPU)
- 新增 系统架构-网关与高性能后端.md: 网关薄代理(健康检查/空闲派发/鉴权/
  base64落盘改URL/无后端→1007) + worker(GPU跑完整app)职责划分、配置文件、
  鉴权(共享密码可轮换)、标注图base64流程、部署、安全注意
- 关键约束: 接口文档不变; 每worker并发=1, 总并发=健康worker数; 多worker可配置
- 技术方案: 加运行位置横幅; torch改GPU(CUDA); handler返回annotated_image_base64
- 任务书v1.4: 标注差异说明; 阶段八返base64+鉴权中间件; 阶段十worker(GPU)部署;
  新增§16网关工作流; DoD拆分worker侧/网关侧
- OFFLINE_ASSETS: 区分worker(GPU torch)与网关(轻量)依赖
2026-06-14 13:55:45 +08:00

14 KiB
Raw Blame History

系统架构:外网网关 + 高性能后端(worker)

本文档描述「旷视五接口」的部署架构拆分。接口文档(对外契约)完全不变——客户端看到的 URL、请求/响应结构、错误码 1001–1008 全部保持原样。本文只改变内部如何处理这些请求。


1. 背景与目标

外网服务器(hair.xiangsilian.com)性能不足以跑 MediaPipe + BiSeNet + 标注图生成等重逻辑。因此拆分为两层:

  • 外网网关(gateway:保持 HTTPS 对外接口不变,自身不跑算法,只做反向代理、健康检查、负载分发、鉴权、标注图托管。资源占用极小,可继续跑在现有外网机。
  • 高性能后端(worker:独立机器,GPU + 32G 内存,跑真正的算法逻辑。通过 http://hair.xiangsilian.com:28187 这类 host:port 暴露。

核心诉求

  1. 对外接口与文档零变化。
  2. 网关可配置多个 worker URL,周期探测可用性,只把请求发给健康的 worker。
  3. 每个 worker 并发 = 1(一次处理一个请求);多 worker 即可并发,总并发 = 健康 worker 数

2. 拓扑总览

                          HTTPS (对外,接口文档不变)
   ┌────────┐   :443    ┌───────────────────────────┐
   │ 客户端 │ ───────▶ │   外网网关 gateway         │
   └────────┘           │  hair.xiangsilian.com      │
                        │  - 反向代理 5 个接口        │
                        │  - 健康检查 worker 池       │
                        │  - 空闲 worker 派发(并发=worker数)│
                        │  - 共享密码鉴权             │
                        │  - 标注图 base64→落盘→URL   │
                        │  - 无可用后端→1007          │
                        └───────┬───────────┬─────────┘
            HTTP + 密码头        │           │
        ┌───────────────────────┘           └─────────────┐
        ▼                                                  ▼
 ┌──────────────────┐                          ┌──────────────────┐
 │ worker #1        │   ...(可配置多个)...      │ worker #N        │
 │ :28187  GPU/32G  │                          │ :xxxxx  GPU/32G  │
 │ 跑完整 app.py    │                          │ 跑完整 app.py    │
 │ + face_analysis  │                          │ + face_analysis  │
 │ 并发=1           │                          │ 并发=1           │
 └──────────────────┘                          └──────────────────┘

3. 职责划分

能力 网关 gateway worker
对外 HTTPS、接口契约
反向代理 5 个接口
算法(MediaPipe/BiSeNet/测量/标注图)
入参校验(大小/格式/分辨率/人脸) ✗(透传)
worker 健康检查 + 池管理 提供 /health
负载分发(挑空闲 worker
鉴权(共享密码) 发送密码 校验密码
标注 PNG 落盘 + 对外 URL 返回 base64
/static/* 静态托管
GPU 不需要

原则:所有业务逻辑只在 worker 实现一份(worker 跑的就是完整 app.py + face_analysis)。网关是无状态的薄层,除了"健康池 + worker 忙闲状态"外不持有业务状态。


4. 网关配置文件

后端 URL 列表、共享密码、各项参数集中在一个配置文件,运维手动维护(密码定期轮换)。

gateway/config.json(示例):

{
  "workers": [
    "http://hair.xiangsilian.com:28187",
    "http://10.0.0.12:28187"
  ],
  "shared_password": "REPLACE_ME_ROTATE_PERIODICALLY",
  "accept_passwords": ["REPLACE_ME_ROTATE_PERIODICALLY"],
  "health_check": {
    "path": "/health",
    "interval_seconds": 8,
    "timeout_seconds": 3,
    "unhealthy_threshold": 2,
    "healthy_threshold": 1
  },
  "dispatch": {
    "per_worker_concurrency": 1,
    "queue_wait_seconds": 30,
    "request_timeout_seconds": 60,
    "retry_on_failure": true,
    "max_retries": 1
  }
}

字段说明:

  • workersworker 基址列表,手动增删。增加一个就多一路并发
  • shared_password:网关调用 worker 时发送的密码。
  • accept_passwords:worker 端用(见 §7)——允许的密码列表,轮换期可同时放新旧两个,实现不停机改密码。网关侧也可放在 worker 的独立配置里。
  • health_check:探测周期、超时、连续失败几次判定下线、连续成功几次判定上线。
  • dispatch.per_worker_concurrency固定为 1(当前约束)。
  • dispatch.queue_wait_seconds:所有 worker 都忙时请求最多排队多久,超时返回 1007。
  • request_timeout_seconds:单次转发到 worker 的超时。
  • retry_on_failure / max_retries:worker 转发失败时是否换一个 worker 重试。

配置变更后网关需 reload(可做成监听文件变更热加载,或重启网关进程)。


5. 健康检查

网关后台周期轮询每个 worker 的 /health(已存在于 app.py,排除在 OpenAPI 之外):

  • 间隔 interval_seconds,每次超时 timeout_seconds
  • 连续失败 unhealthy_threshold 次 → 标记下线,停止派发。
  • 重新连续成功 healthy_threshold 次 → 标记上线,恢复派发。
  • 健康池 = 当前在线的 worker 集合。

主动检查 + 被动剔除双保险:除周期探测外,转发请求时若 worker 连接失败/超时,立即把它标记为不健康并(按配置)换一个 worker 重试。

worker 的 /health 建议返回 模型就绪状态:只有 MediaPipe / BiSeNet 权重都加载完成才返回 200,否则返回 503——避免请求被派发到尚未热好的 worker。


6. 负载分发与并发

每个 worker 并发 = 1,所以分发逻辑很简单:

  1. 网关维护每个健康 worker 的忙/闲状态。
  2. 新请求到来:从健康池里选一个空闲 worker,标记为忙,转发;收到响应(或失败)后标记为闲。
  3. 若当前无空闲 worker(全忙):请求进入队列等待,直到有 worker 空闲或等待超过 queue_wait_seconds(超时返回 1007)。
  4. 健康池为空(无可用后端):直接返回 1007。
  • 总并发能力 = 健康 worker 数量。加机器即扩并发。
  • 选空闲 worker 的策略:任意(如先到先得 / 轮转空闲),并发=1 下无需最少连接数算法。

7. 鉴权(共享密码)

:28187 在公网可直接访问,必须鉴权防止他人直接打 worker。

  • 网关 → worker:每个转发请求带密码头,如 X-Internal-Token: <shared_password>
  • worker 校验worker 侧配置文件持有 accept_passwords 列表,收到请求校验 X-Internal-Token 是否在列表内;不匹配返回 HTTP 401,不进入业务逻辑
  • 轮换:运维改密码时,先把新密码加进 worker 的 accept_passwords(此时新旧都接受)→ 再把网关 shared_password 切到新值 → 确认无旧密码流量后从 worker 移除旧密码。全程不停机。
  • 密码仅存配置文件,不写日志、不进 git(配置文件加入 .gitignore,仓库只放 config.example.json)。

建议叠加防火墙:worker 防火墙只放行网关来源 IP(纵深防御)。密码是应用层兜底。


8. 单次请求处理流程

客户端 ──(HTTPS, multipart/json, 接口文档原样)──▶ 网关
   │
   ├─ 1. 选一个空闲健康 worker(无则排队/1007)
   ├─ 2. 原样转发请求体 + 加 X-Internal-Token 头
   │        ──(HTTP)──▶ worker
   │                       ├─ 校验密码(失败→401,网关视为该 worker 异常)
   │                       ├─ 跑完整业务(校验/MediaPipe/BiSeNet/测量/标注图)
   │                       └─ 返回标准信封;图片字段以 base64 形式(见 §9
   ├─ 3. 收到 worker 响应,标记该 worker 空闲
   ├─ 4. 若响应含 base64 图片 → 落盘到网关 /static/annotations/{uuid}.png
   │        → 把字段改写成对外 URL(见 §9)
   └─ 5. 把最终(符合接口文档的)响应返回客户端
  • 网关不解析也不校验业务入参,原样透传(worker 负责全部校验与错误码)。worker 返回的 10011008 由网关透传给客户端。
  • 网关只在「无后端/排队超时」时自行返回 1007。

9. 标注图处理(base64 → 落盘 → URL 改写)★关键

接口文档里多个接口返回图片 URL(接口1 标注图、接口3 标记图、接口5 发际线图)。拆分后:

  1. worker 不落盘、不拼 URL,而是把生成的 PNG 以 base64 放进响应的约定字段返回给网关。
    • 约定:worker 用 *_base64 字段承载图片,例如接口1 返回 annotated_image_base64 而非 annotated_image_url
  2. 网关收到后:对每个 *_base64 字段——
    • 解码 → 保存到网关本地 static/annotations/{uuid}.png
    • 删除该 base64 字段,新增对应的 *_url 字段,值为 https://hair.xiangsilian.com/static/annotations/{uuid}.png
  3. 客户端最终看到的字段名/URL 与接口文档完全一致(如 annotated_image_url)。

网关持有一份「接口 → 图片字段」映射表(接口1: annotated_image ↔ 接口5: hairline_image 等),按表把内部 *_base64 转成对外 *_url。映射表以接口文档为准。

代价:内部 HTTP 多传一份 base64(图片放大约 1.33×)。worker↔网关若跨网络,单图 ~100KB–1MB 量级,可接受。

静态文件清理:网关 static/annotations/ 会持续增长,需加定期清理(按时间或容量,定时任务),与拆分前同样的问题,由网关侧负责。


10. 错误处理

场景 返回 由谁
业务错误(无人脸/分辨率/非正面/超大/格式…) 10011008(原样) worker 产生,网关透传
无可用 worker / 全忙排队超时 1007 系统错误 网关
worker 转发失败(连接/超时/5xx/401) 按配置换 worker 重试;重试耗尽 → 1007 网关
  • 复用 1007(系统错误)表示「基础设施层不可用」,不新增错误码,接口文档不动。
  • 局限:调用方无法从错误码区分"算法失败"与"后端全挂"(都是 1007)。如需区分,可在 message 文案上体现(如"后端服务暂不可用,请稍后重试"),但 code 保持 1007。

11. worker 侧相对单机方案的变化

worker 跑的就是「接口1 技术实现方案」里描述的完整逻辑,但有几处因拆分/硬件而变:

  1. GPU 加速worker 有独立 GPUBiSeNet 改用 CUDA 推理(torch GPU 版),比 CPU 快很多;MediaPipe 仍 CPUPython solutions API 仅 CPU)。
  2. 不落盘、返回 base64:标注图相关接口的 handler 改为返回 *_base64,不再保存到本地 /static、不拼 URL(改由网关做,见 §9)。
  3. /health 反映就绪:模型加载完才返回 200。
  4. 鉴权中间件worker 增加一个校验 X-Internal-Token 的中间件/依赖(见 §7)。
  5. 资源宽裕:32G 内存 + GPU,无需单机方案里 2核4G 的并发限制与降级开关;但 worker 自身仍是并发=1(由网关保证,不向 worker 发并发请求;worker 可不做内部并发控制,但建议 uvicorn 单 worker 进程以省显存)。

单机方案文档(接口1 技术实现方案)描述的「方案A/B、虹膜标定、标注图、误差验证」全部不变,只是运行位置从外网机挪到 GPU worker,并启用 GPU。


12. 部署

网关(外网机,hair.xiangsilian.com

  • 新增 gateway 应用(轻量 FastAPI/asgi 代理)。
  • nginx 把 443 → 网关进程;网关再转发到 worker 池。
  • 托管 /static/*(标注图落盘目录)。
  • 配置文件 gateway/config.json(含 worker 列表 + 密码)。
  • systemd 管理网关进程。

workerGPU 机,:28187

  • 部署完整 app.py + face_analysis + 模型权重(见 OFFLINE_ASSETS.md)。
  • torch 用 GPU 版CUDA),其余依赖同单机方案。
  • uvicorn 监听 0.0.0.0:28187(仅经防火墙放行网关)。
  • 配置文件持有 accept_passwords
  • systemd 管理 worker 进程;/health 供网关探测。

13. 安全注意

  • :28187 公网可达 + HTTP 明文:密码头会明文走公网。强烈建议给 worker 也套一层 TLSworker 前置 nginx 终止 HTTPS,或网关↔worker 走内网/VPN/IP 白名单),否则密码可被中间人嗅探。
    • 若短期内只能 HTTP,务必靠防火墙 IP 白名单把 worker 限制为只接受网关来源,密码作为应用层兜底。
  • 密码不入 git、不写日志。
  • 网关对转发的请求体大小设上限(如 ≤2MB),防止被超大 body 拖垮。

14. 待确认 / 后续

  1. worker↔网关传输是否加 TLS:当前 §13 标为强烈建议。若运维能给 worker 配 HTTPS 或限定内网,安全性更好。请确认部署条件。
  2. worker host:port 形态hair.xiangsilian.com:28187 是端口转发到 GPU 机,还是 GPU 机直接持有该域名?影响防火墙与 TLS 方案。
  3. 多 worker 的物理分布:是否都在同一内网?若跨公网,base64 图片传输与密码明文风险都需重新评估。
  4. 静态图清理策略:网关 static/annotations/ 的保留时长 / 清理触发条件。

文档版本: v1.0 创建日期: 2026-06-14 配套:接口1 技术方案 v2.0 / 开发任务书 / OFFLINE_ASSETS.md 关键约束: 接口文档不变;每 worker 并发=1;无后端→1007;标注图 base64→网关落盘→URL