Files
dp/docs/production-architecture.md
2026-07-17 02:10:08 +08:00

285 lines
6.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 生产架构说明
## 目标
本项目用于把一个逻辑尺寸为 `14880 x 3510` 的 LED 大屏内容,拆分到两台 GPU 输出服务器本地渲染。
生产形态:
- 控制服务运行 FastAPI,负责场景、动作、时间同步、ACK、状态监控。
- 左 GPU 服务器打开 `/output/left`,只显示左半屏 tile。
- 右 GPU 服务器打开 `/output/right`,只显示右半屏 tile。
- 每台 GPU 服务器输出 4 路 4K 到拼控或 LED 控制器。
- 拼控或 LED 控制器完成最终物理拼接。
WebSocket 不传大视频流,只传状态、命令、时间、ACK 和性能指标。画面由两台 GPU 服务器本地渲染。
## 逻辑拓扑
```text
控制台 /
http://control:8000/
|
v
FastAPI 控制服务
场景管理 / WebSocket Hub / SyncCoordinator / 状态接口
| |
prepare/commit/action status/telemetry/ACK
| |
-------------------------------
| |
v v
左 GPU 输出服务器 右 GPU 输出服务器
/output/left /output/right
left tile 本地渲染 right tile 本地渲染
4 路 4K 输出 4 路 4K 输出
| |
---------> 拼控 / LED 控制器 <-
14880 x 3510
```
## Tile 切分
完整逻辑画面:
```text
|-------------------------- 14880 --------------------------|
|------------ left 7440 ------------|-------- right 7440 ----|
```
`config/tiles.json` 定义:
```text
left:
x=0
y=0
width=7440
height=3510
right:
x=7440
y=0
width=7440
height=3510
```
输出端页面使用完整逻辑大屏坐标系,再根据自己的 tile 做视口偏移。Three.js 项目中建议使用:
```js
camera.setViewOffset(
fullWidth,
fullHeight,
tile.x,
tile.y,
tile.width,
tile.height,
);
```
## 每台服务器 4 路 4K
每台 GPU 服务器建议配置为 `2 x 2` 逻辑桌面:
```text
7680 x 4320
```
四路输出映射:
```text
1: x=0, y=0, 3840 x 2160
2: x=3840, y=0, 3840 x 2160
3: x=0, y=2160, 3840 x 2160
4: x=3840, y=2160, 3840 x 2160
```
单侧业务 tile 是 `7440 x 3510`,可以放入该逻辑桌面。
## 同步协议
### 场景切换
控制台调用:
```text
POST /api/scenes/{scene_id}/switch
```
服务端生成同一个 `command_id`,先广播:
```text
prepare_scene(command_id, apply_at_ms, state, scene)
```
输出端收到 `prepare_scene` 后:
1. 预加载资源。
2. 等待至少两个 RAF,让浏览器完成布局和首帧准备。
3. 如果存在 `window.ledPlatformPrepareScene`,等待业务方的真实预渲染/超分完成。
4. 回 ACK`status=prepared`
服务端 `SyncCoordinator``target_tiles` 中的 `left``right` 都返回 `prepared` 后,才广播:
```text
commit_scene(command_id, apply_at_ms, state, scene)
```
输出端收到 `commit_scene` 后,等到统一的 `apply_at_ms` 再切换画面,并回 ACK
```text
status=committed
```
这样谁渲染慢就等谁,两个输出端都准备好后才同步放行。
### 局部动作
控制台调用:
```text
POST /api/actions
```
支持动作:
```text
page.next
page.prev
energy.mode
security.alert
timeline.pause
timeline.resume
```
局部动作目前直接按 `apply_at_ms` 调度并回 ACK
```text
status=action_committed
```
如果后续某些局部动作也需要超分或重资源准备,可以复用场景切换的 prepare/barrier/commit 模式。
## 画面回显
控制台内置两个只读预览 iframe,并把它们无缝拼成一块完整画面:
```text
/output/left?preview=1
/output/right?preview=1
```
预览模式通过 `/ws/admin` 接收状态和命令,不连接 `/ws/output/{tile_id}`,因此:
- 不注册为真实输出节点。
- 不发送 `prepared``committed``telemetry` ACK。
- 不会提前释放同步 barrier。
- 只用于控制台观察画面。
真实生产输出端仍然打开:
```text
/output/left
/output/right
```
## 时间同步
输出端通过 `clock_ping` / `clock_pong` 估算服务端时间:
```text
serverNowMs = Date.now() + serverOffsetFromDateMs
```
RTT 使用 `performance.now()` 估算,服务端时间偏移使用客户端 wall clock 的发送时间和返回时间中点估算。
渲染和动画应基于统一时间轴:
```js
const t = serverNowMs() - sceneStartedAtMs;
renderSceneAt(t);
```
不要让左右输出端各自累计本地 delta:
```js
// 不推荐
animation += localDeltaTime;
```
## 状态观测
控制台显示:
- 输出节点数量。
- 每个输出节点 FPS、frame time、RTT、clock offset。
- 最近命令回显。
- 每个 tile 的 `prepared``committed``late``error` 状态。
- barrier 放行耗时。
接口:
```text
GET /api/sync/status
GET /healthz
```
## 为什么不传视频流
单侧半屏未压缩数据量约为:
```text
7440 x 3510 x 4 bytes x 60 fps ~= 6.3 GB/s
```
左右两侧合计超过 `12 GB/s`。普通网络视频流还需要编码、传输、解码,会带来延迟、画质损失、文字细线压缩失真,以及新的编码/解码同步问题。
本系统选择“控制服务发命令,输出端本地渲染”,更适合高分辨率 LED 墙。
## 生产验收指标
建议关注 P95/P99
```text
60 FPS:
P95 frame time < 16.67ms
P99 frame time < 20ms
30 FPS:
P95 frame time < 33.33ms
P99 frame time < 40ms
```
同步指标:
```text
WebSocket RTT < 10ms
clock offset < 5ms
left/right 都返回 prepared
left/right 都返回 committed 或 action_committed
不得频繁出现 late ACK
```
硬件建议:
- 两台输出服务器尽量使用同型号 GPU、同驱动、同浏览器版本。
- 电源策略设置为高性能。
- 关闭系统休眠、屏保、自动更新弹窗。
- 拼控侧开启输入缓存、帧同步或延迟对齐能力。
- 生产环境使用 PTP 或内网 NTP。
## 边界
能保证:
- 两边业务状态一致。
- 两边都准备好后才释放场景提交。
- 两边按统一服务端时间点提交。
- 动画长期不漂移。
- 命令和性能可观测。
不能单独保证:
- 两张不同 GPU 的物理扫描完全同相。
如果必须达到广播级帧同步,需要硬件层支持 Genlock / Frame Lock / 专业视频墙控制器。