将 TLUSTY/SYNSPEC 拆为各自独立的 enabled/policy/strategies 阶段,
以策略链自动弹栈取代单级 seed_step 布尔回退;定向修复 2026-08-02
僵尸任务涡旋事故;新增节点并发配额热调;前端详情页从 1412 行巨型
视图拆为薄控制器 + detail 子模块,并补齐工具层与单测。
引擎与调度(task_engine_decoupling_design.md)
- models.rs: 新增 StagePolicy / EngineStageConfig / TaskSpec 阶段字段、
normalize_compat() 校正旧版在途消息策略链、failed_stage 归因
- scheduler.rs: resolve_dispatchable_chain 派发门控、
trigger_strategy_fallback 按 failed_stage 精确弹栈;启动期
force_recompute/skip_converged(默认)/skip_failed 三策略
- db.rs: tasks 表 +7 列持久化阶段配置;终态守卫
(mark_grid_point_running 仅 pending/queued→running;
record_task_report 拒绝迟到失败翻黑 converged);策略弹栈快照
僵尸涡旋修复(runbook-20260802-zombie-vortex-fix.md)
- 全链路跨库活性交叉校验:派发/claim/孤儿回收/回退统一查 MQ 队列活性,
活则放行、死则清僵尸,结构性消除"每点重复派发"
- stop/重启卫生:清队列同步 delete_tasks_by_ids,杜绝遗留 pending 行
- report_task: 幂等吸收 + 409 区分迟到冗余结果,仅 state_changed 时回退
- MQ: NULL workflow_name 回填 __legacy__、requeue 后迟到上报被 403 竞态修复
动态 CPU 配额(dynamic_cpu_slots_design.md)
- admin.rs: POST /admin/nodes/:id/quota(Option<Option<i32>> 区分
缺字段/显式 null);nodes 表 +admin_max_slots
- worker.rs: effective_max_slots = min(admin, physical),心跳下发原子生效
科学产物保全(tlusty_result_artifacts.md)
- runner.rs: SYNSPEC 启动前快照 fort.12/fort.14 → .bfac/.emflux 防覆盖
- 半失败点(大气收敛+光谱失败)改判 Failed 并写入 note;仅 SYNSPEC
场景不再恒判失败;撤销归档 LRU 200 上限改为永久保留
- executor.rs: 透传 synspec_params 数值参数(此前固定 None)
前端(dashboard/)
- workflowDetail.js 1412→328 行,拆出 views/detail/{ctx,overview,
pointsTable,parSets,pointPanel}.js,AbortController 治理监听/请求生命周期
- 删除 wfActions.js,新增 wfEnginePanel.js(双阶段三维配置编辑面板)
- 新增 utils/{errors,format,icons,polling,yamlStage}.js 纯函数模块
- 路由级动态 import 代码分割;节点配额三点菜单 + Modal 管理
- 首次引入 node:test 单测(format/polling/yamlStage/psCache,644 行)
- 系统性补齐 a11y:skip-link、ARIA、Tab 键盘漫游、toast 关闭、退出动画
文档与工具
- 新增 6 篇设计/调研:引擎解耦、动态配额、涡旋 runbook、
光谱正确性分析、收敛判断、产物归档
- PIPELINE/design/api/database 等协同重写为分布式 C/S 架构口径
- scripts/fetch_results.sh 跨节点产物备份;import_results 按 cno 升序导入
- workflows/sdB_cno.yaml: 新增 tlusty/synspec_stage 配置块,修正 wstart 笔误
154 lines
9.7 KiB
Markdown
154 lines
9.7 KiB
Markdown
# DCTS 系统架构 (System Architecture)
|
||
|
||
> 介绍 DCTS (Distributed Computing TLUSTY/SYNSPEC) 的整体设计架构、Master-Worker 拓扑结构、任务生命周期流转及容错机制。
|
||
|
||
---
|
||
|
||
## 1. 架构拓扑 (Topology)
|
||
|
||
DCTS 采用**中心化控制、分布式离散执行 (Master-Worker)** 的拓扑设计:
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
subgraph Master Node ["Master 服务端 (server)"]
|
||
API["Axum HTTP REST API"]
|
||
DB[(主数据库 dcts.db)]
|
||
MQ[(任务队列 dcts_queue.db)]
|
||
Scheduler["Grid Scheduler 网格调度器"]
|
||
SeedsStorage["本地种子库 seeds/*.7"]
|
||
|
||
API <--> DB
|
||
API <--> MQ
|
||
Scheduler --> MQ
|
||
Scheduler --> DB
|
||
end
|
||
|
||
subgraph Compute Nodes ["分布式计算节点集群 (node)"]
|
||
Worker1["Worker 节点 1 (Node Daemon)"]
|
||
Worker2["Worker 节点 2 (Node Daemon)"]
|
||
WorkerN["Worker 节点 N (Node Daemon)"]
|
||
end
|
||
|
||
Worker1 -- "1. 心跳/抢占任务 (Claim)" --> API
|
||
Worker1 -- "2. 下载种子/基础数据" --> API
|
||
Worker1 -- "3. 汇报结果/上传 .7 大气" --> API
|
||
|
||
Worker2 -- "免凭据注册申请 / 审批后心跳" --> API
|
||
WorkerN -- "免凭据注册申请 / 审批后心跳" --> API
|
||
|
||
API --> SeedsStorage
|
||
```
|
||
|
||
---
|
||
|
||
## 2. 核心组件职责
|
||
|
||
### 2.1 Master 服务端 (`server` & Web `dashboard`)
|
||
- **工作流与多任务隔离**:支持多工作流并发隔离运行(`grid_points` 与 `task_queue` 增加 `workflow_name` 复合唯一索引),调度与重置操作严格隔离在单工作流作用域内。支持旧版 SQLite 数据库启动时无缝平滑迁移。
|
||
- **安全中间件与 RBAC**:基于角色访问控制 (Admin / Node / Public) 划分 API 权限,内嵌 Bearer Token 校验、**滑动窗口(sliding window)限流中间件**与 CORS 跨域控制,防御侧信道攻击与暴力破解。
|
||
- **节点凭据生命周期管理**:支持 Worker 节点注册申请、管理员审批授权、Token 重新颁发(旧 token 失效)与节点停用/启用全生命周期管理。
|
||
- **任务调度与分配**:调度器将 pending 网格点推入 `mq` 队列;Worker 以 **pull 抢占式** 领用任务(详见 [§5 任务调度模型](#5-任务调度模型-task-scheduling))。
|
||
- **状态维护与心跳监测**:后台离线检测线程定期标记超时未心跳的节点为 `offline`,并能将僵挂在超时节点上的任务自动回收到队列中(Requeue)。
|
||
- **ESM 模块化 Web 看板**:前端采用 ESM 模块解耦设计(`state.js`, `api.js`, `components/`),支持节点凭据管理、工作流控制与全局 Toast 通知。
|
||
|
||
### 2.2 Worker 计算节点 (`node`)
|
||
- **身份标识**:`DCTS_NODE_ID` 未指定或为空时,自动生成基于随机 UUID 的节点 ID(`node-<uuid>`)。Worker 是计算服务,每个物理设备部署一个 node 进程(多 slot 并行由 `DCTS_MAX_SLOTS` 控制),无需在同一设备运行多副本。
|
||
- **环境自适应预热 (Bootstrap)**:启动时核对本地 `./runtime` 运行依赖,缺失时自动向 Master 拉取可执行文件与二进制数据。
|
||
- **任务抢占与执行 (Claim & Execute)**:根据并发配置轮询抢占任务,调用 `common` 启动子进程链(tlusty / synspec)。
|
||
- **种子检索与回传 (Seed Sync)**:计算成功后将收敛的大气结构文件(`.7`)与状态 JSON 汇报回服务端。
|
||
|
||
---
|
||
|
||
## 3. 任务生命周期 (Task Lifecycle)
|
||
|
||
网格计算点从创建到完成的状态流转如下图所示:
|
||
|
||
```mermaid
|
||
stateDiagram-v2
|
||
[*] --> Pending : 工作流注册生成网格点
|
||
Pending --> Queued : 调度器推入 mq 队列
|
||
Queued --> Running : Worker 成功 Claim 抢占
|
||
|
||
state Running {
|
||
[*] --> ExecutingChain
|
||
ExecutingChain --> ColdStartChain : 默认冷启动 (lte->nc->nl)
|
||
ColdStartChain --> Synspec : 物理收敛
|
||
ColdStartChain --> SeedStepChain : 冷启动发散且有可邻近种子
|
||
SeedStepChain --> Synspec : 热启动收敛
|
||
}
|
||
|
||
Running --> Converged : 计算成功 & 上传 .7 产物
|
||
Running --> Pending : Worker 心跳超时/掉线 (Requeue, 直接回 Pending 等待重新派发)
|
||
Running --> Failed : 无可邻近种子回退 / 彻底发散
|
||
|
||
Converged --> [*]
|
||
Failed --> [*]
|
||
```
|
||
|
||
> 状态值与 [`GridPointStatus`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/common/src/models.rs#L237) 一一对应:
|
||
> `pending` → `queued` → `running` → `converged` / `failed`。
|
||
> Requeue 把 `running` 直接重置回 `pending`(不经过 `queued`),由调度器下一轮重新推入队列。
|
||
|
||
---
|
||
|
||
## 4. 容错与高可用设计 (Fault Tolerance)
|
||
|
||
1. **分布式无状态 Worker & 上报阶梯退避**:Worker 节点不保存持久运行控制状态,异常宕机不会损坏主数据集。计算结果向 Master 上报时,具备多达 8 次指数阶梯容灾回退(最大间隔 60 秒,覆盖超 2 分钟断断连长窗),稳健保障长时间高密物理算单不受瞬时组网闪断或服务端短时上线切换干预。
|
||
2. **零文件扫描与连接并发缩流**:底层任务分批取配、排队清洗与邻接优化(Seed-Stepping)全链线依赖常驻内存的 SQlite 主从精算并调优收敛连接池配置(主库=8,队列=4 减免本地排他写冲突并发挂断);去除了历史残存的高损及同步阻塞磁盘遍历 API,在确保零卡死响应的前提下提升查询搜索效力。
|
||
3. **任务超时与流控平稳保护 (Stale & Requeue)**:服务端后台定期向已超时死挂的 `Running` (默认 >1800 秒)作业予以强退回转为 `Pending`;此外当操作维护员发起暂停或终止工作流行为时,将仅平滑洗退待调 `Queued` 项,悉心保育在途已投的 Worker 数值演算完整出计算归表,防假命题竞合。
|
||
4. **策略链回退与种子匹配**:若某点当前策略失败,服务端弹出策略链首项(`trigger_strategy_fallback`),下一顺位为 `seed_step` 时在全局种子库中按**有向 CNO 距离**(富方向重罚 4×、贫方向 1×,非对称)匹配最近邻收敛 `.7` 大气作为热启动种子;无种子则保持 pending 待种子出现后自愈。失败现场经**白名单归档**(`result_dir`)保留科学产物,沙盒则在任务结算后清理(详见 `tlusty_result_artifacts.md` / `design.md`)。
|
||
|
||
---
|
||
|
||
## 5. 任务调度模型 (Task Scheduling)
|
||
|
||
### 5.1 Pull 抢占式模型(非服务端负载分发)
|
||
|
||
DCTS 的任务分发是 **Worker 主动拉取(pull)** 模型,**不是**服务端按节点负载推派(push):
|
||
|
||
- **调度器**([`schedule_pending_tasks`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/scheduler.rs#L153))只负责把 `pending` 网格点推入 `mq` 队列(`push_task`),不参与"分给哪个 node"的决策。
|
||
- **Worker**([领用主循环](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/node/src/worker.rs#L275))只要 `active_slots < max_slots`,就持续向服务端发起 `claim` 请求抢任务。
|
||
- **服务端** [`pop_task`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/mq/src/sqlite_queue.rs#L145) 的出队决策**只看任务自身属性**,不看谁来领用:谁先抢到写锁、谁就拿到队首任务。
|
||
|
||
出队排序(难度优先 → 同难度先进先出):
|
||
|
||
```sql
|
||
SELECT task_id, payload FROM task_queue
|
||
WHERE status = 'pending'
|
||
ORDER BY wave ASC, created_at ASC
|
||
LIMIT 1
|
||
```
|
||
|
||
### 5.2 为什么不做服务端负载分发
|
||
|
||
关键观察:**Worker 满载后会主动停止领用**([worker.rs](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/node/src/worker.rs#L280-L281)):
|
||
|
||
```rust
|
||
let active = self.active_slots.load(Ordering::Acquire);
|
||
if (active as usize) < self.effective_max_slots.load(Ordering::Acquire) {
|
||
match self.claim_task().await { /* 抢任务 */ }
|
||
} else {
|
||
sleep(Duration::from_secs(2)).await; // 满载:不 claim,等槽位释放
|
||
}
|
||
```
|
||
> `effective_max_slots` = `min(admin_max_slots, physical_max_slots)`,管理员配额经心跳响应下发、动态生效(见 `dynamic_cpu_slots_design.md`)。
|
||
|
||
由此分两种工况:
|
||
|
||
| 工况 | 条件 | 是否会扎堆 | 负载分发是否需要 |
|
||
|------|------|-----------|-----------------|
|
||
| **稳态** | 任务数 ≥ 所有 node 总槽位 | 否——各 node 抢满 `max_slots` 即停止 claim,空出槽位者再去抢,**天然均衡** | 不需要 |
|
||
| **稀疏态** | 任务数 < 总槽位(如调试用 2 个点) | 是——最快的 node 连续抢光少量任务 | 收益极小,省的仅是几个任务的串行墙钟时间 |
|
||
|
||
稳态(网格规模任务,如 sdB_cno 的 432 点)是实际生产场景,该场景下 pull 模型已自动达成负载均衡。稀疏态的扎堆是固有现象且代价可忽略(任务跑完即结束)。
|
||
|
||
引入服务端负载分发的**代价**反而更高:破坏 FIFO 可预测性、带来心跳负载数据滞后导致的退让抖动、增加队首饥饿风险。综合权衡,**当前 pull 模型是刻意选择,无需改为服务端负载分发**。
|
||
|
||
> **稀疏态调试建议**:若调试时确实希望任务分散到多个 node 观察,最省事的做法是把各 node 的 `DCTS_MAX_SLOTS` 设为 `1`——每个 node 抢 1 个即满载停止,自然分散,零代码改动、零风险。
|
||
|
||
### 5.3 出队优先级语义(wave + FIFO)
|
||
|
||
- **`wave ASC`(难度优先)**:网格点按 CNO 丰度之和(`cno_sum`)升序分组,低丰度(低难度、更易收敛)的点先出队。设计意图是"先易后难"——先跑出收敛点作为种子库,后续高难度点可借热启动种子收敛。
|
||
- **`created_at ASC`(同难度 FIFO)**:同一 `wave` 内严格按推入队列的时间先后出队。
|
||
- **跨工作流公平**:出队不按 `workflow_name` 分组,多个并发工作流的低难度点会先于任何工作流的高难度点出队,避免某工作流在高难度 wave 上饿死其他工作流的低难度点。
|