单机部署(小型 / POC) 适用:日均订单 < 1k、模板 < 100、用户素材 < 100GB。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ┌─────────────────────────── 单机 ───────────────────────────┐ │ │ │ Nginx (80/443) │ │ ├── / → pf-app (静态) │ │ ├── /editor/ → pf-editor (静态) │ │ ├── /manage/ → pf-manage (静态) │ │ ├── /api/ → pf-service:3000 │ │ └── /face/ → pf-face-service:3010 │ │ │ │ pf-service:3000 (Bun process, systemd unit) │ │ pf-face-service:3010 (uvicorn, systemd unit) │ │ │ │ MySQL:3306 │ │ Redis:6379 │ │ MinIO:1280 (S3) + 9001 (Console) │ │ │ └────────────────────────────────────────────────────────────┘
3 个 systemd 单元 + 3 个 docker compose 服务,部署成本最低。
分布式部署(生产推荐) 适用:日均订单 ≥ 10k、需要稳定的合成吞吐与高可用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 ┌────────────────────┐ │ CDN / 对象存储 │ ← 用户素材 / 成品对外回源 └─────────┬──────────┘ │ ┌─────────▼──────────┐ │ 反向代理 (LB) │ Nginx / Caddy / ALB │ TLS 终止 + WAF │ └─────────┬──────────┘ │ ┌───────────┬───────┼─────────┬──────────────────┐ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ ┌─────────┐ ┌─────────┐ ┌─────────────┐ ┌─────────────────────┐ │ pf-app │ │pf-editor│ │ pf-manage │ │ pf-service (API) │ │ 静态托管│ │ 静态托管│ │ 静态托管 │ │ N 个 Bun 进程 │ └─────────┘ └─────────┘ └─────────────┘ │ - 无状态 │ │ - 水平扩展 │ └─────────┬───────────┘ │ ┌───────────────────────────────────────┼──────────────────────────┐ │ │ │ ▼ ▼ ▼ ┌─────────────────────┐ ┌────────────────────┐ ┌──────────────────────┐ │ pf-service (Worker) │ │ pf-face-service │ │ MinIO (S3 集群) │ │ 合成专用进程组 │ │ N 个副本 │ │ 多节点 / Erasure │ │ + Puppeteer 实例池 │ └────────────────────┘ └──────────────────────┘ └─────────┬───────────┘ │ ┌─────────▼──────────┐ ┌──────────────────┐ │ MySQL (主从) │ │ Redis (Cluster)│ └────────────────────┘ └──────────────────┘
关键拆分点 1. API 进程 vs 合成 Worker pf-service 是同一份代码,但部署时建议拆成两组进程 :
进程组
作用
config.server.jobs.composition.enabled
API 组
服务 HTTP 请求,水平扩展
false
Worker 组
跑 orderTemplateCompositionJob,独占 Puppeteer
true
避免合成进程的 CPU/IO 抖动影响 HTTP 响应延迟。
2. Puppeteer 进程隔离 Worker 进程内 Puppeteer 容易出 OOM / 僵尸进程。建议:
每个 Worker 持有 1–2 个 Browser 长连接
每条合成任务用独立 Page,处理完即关
进程内累计渲染 N 次后 Browser 重启(避免内存泄漏累积)
容器化时给 Worker 单独的 cgroup(CPU/内存上限),互不挤占
3. 对象存储 MinIO 单节点适合开发;生产建议:
多节点 MinIO(Erasure Code 4+2 起)或直接用 S3 / OSS / COS
把 endpoint 改成对外 CDN 域名时,注意分预签名(内部直传)和对外读取(带签名 URL 或公开桶)两条路径
冷热分离:30 天后的成品迁到 STANDARD_IA / 归档存储
4. 数据库
主写从读:模板列表、订单列表都偏读
备份:mysqldump 每日 + binlog 实时
长尾大表:order_template_submission_items 增长最快,按 createdAt 月份分表/归档
5. Redis
缓存:字体/分类/模板元数据(命中率高、变更频率低)
任务队列(如果你把合成调度切到队列模式):Streams / List
会话:JWT 黑名单
部署成 Cluster 或 Sentinel,单点 Redis 是生产隐患
静态前端发布 三个 Quasar/Vite 工程都是构建产物:
1 2 3 bun run --filter @wangijun/pf-app build bun run --filter @wangijun/pf-editor build bun run --filter soybean-admin build
把对应 dist/ 目录推到 Nginx / 对象存储 + CDN 即可。注意:
配置 pf-service 域名注入到前端构建(一般通过 .env.production 或 build-time 替换)
SPA 路由:Nginx try_files $uri /index.html;
上 CDN 时区分版本目录或带 hash 文件名
反向代理示例(Nginx) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 server { listen 443 ssl http2; server_name pf.example.com; location / { root /var/www/pf-app; try_files $uri /index.html; } location /api/ { proxy_pass http://pf_service_upstream/; proxy_set_header Host $host ; proxy_read_timeout 120s ; client_max_body_size 220m ; } location /face/ { proxy_pass http://pf_face_service_upstream/; proxy_read_timeout 30s ; } } upstream pf_service_upstream { server pf-service-1 :3000 ; server pf-service-2 :3000 ; keepalive 64 ; }
systemd 单元示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [Unit] Description =Printing Factory ServiceAfter =network.target[Service] Type =simpleUser =pfWorkingDirectory =/opt/printing-factory/apps/pf-serviceExecStart =/usr/local/bin/bun run src/index.tsRestart =on -failureRestartSec =5 Environment ="NODE_ENV=production" [Install] WantedBy =multi-user.target
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [Unit] Description =Printing Factory Face ServiceAfter =network.target[Service] Type =simpleUser =pfWorkingDirectory =/opt/printing-factory/apps/pf-face-serviceExecStart =/usr/local/bin/uv run uvicorn app.main:app --host 0.0 .0.0 --port 3010 Restart =on -failureRestartSec =5 [Install] WantedBy =multi-user.target
容器化要点
镜像分层:依赖层(bun install / uv sync)单独打 → 业务层只拷源码
Puppeteer 依赖 Chromium 系统库(libnss3 / libatk 等),用官方 node/oven/bun 镜像时要补
人脸服务镜像需要 OpenCV 系统库(libgl1、libglib2.0-0)
健康检查走 /health,K8s readinessProbe + livenessProbe
监控与告警 最少接入这些指标,再考虑后续观测能力:
指标
来源
告警阈值参考
HTTP 5xx 率
反向代理
> 1% 持续 5min
API P95 延迟
pf-service
> 1s
合成 pending 队列长度
DB query / 指标埋点
> 100 持续 10min
合成失败率
DB query
> 5%
合成单条耗时 P95
指标埋点
> 30s
Puppeteer 进程内存
容器指标
> 限额 80%
MinIO 可用空间
exporter
< 20%
face-service /health
探针
非 ok 任何时长
安全 checklist
下一步