# 动态调整计算节点 CPU 核数(并发槽位)设计方案 > **状态:已实现**(`effective_max_slots` + 心跳响应下发配额 + `/api/admin/nodes/:node_id/quota` + > 节点详情管理弹窗)。下文为设计原文,保留作实现参照;实际实现以 `crates/node/src/worker.rs`、 > `crates/server/src/api/admin.rs`、`dashboard/src/components/nodesTable.js` 为准。 ## 1. 需求背景 目前 DCTS 的计算节点 (Worker) 在启动时通过本地配置确定最大计算槽位 (`max_slots`),并在心跳和注册时上报给服务端。如果在不重启 Worker 进程的情况下,因为设备资源被其他用户借用等原因,需要临时减少该节点分发的任务数(即缩减 CPU 核数/并发槽位)。 ## 2. 现有架构分析 - **Pull 模式**:任务的领用是由 Worker 主动发起 `POST /api/task/claim` 的。Worker 内部根据自身的 `max_slots` 控制并发领用的数量。 - **状态同步**:Worker 定期发送 `POST /api/node/heartbeat` 汇报当前的 `active_slots`(正在运行的任务数)和资源负载。 因为是 Pull 模式,如果在服务端强行拒绝 `claim`(例如当节点的活跃任务超过管理员设定的配额时返回空),会导致 Worker 不断进行无效的轮询(Busy Loop),浪费网络和日志资源。因此,最优解是**让 Worker 能够感知到服务端下发的配额上限,并动态调整其本地的并发控制器**。 ## 3. 设计方案:基于心跳响应的配额下发机制 (Heartbeat-based Quota Sync) ### 3.1 数据库与服务端改造 1. **DB 结构更新**: 在服务端的 `nodes` 表中新增一个可空字段 `admin_max_slots` (INTEGER, Nullable),用于保存管理员强制指定的并发上限。 2. **新增管理 API**: - 接口:`POST /api/admin/nodes/:node_id/quota` - 权限:Admin 角色 - 请求体:`{"admin_max_slots": 4}`(传 `null` 表示解除限制,恢复节点的物理槽位上限) 3. **心跳响应扩展**: 修改 `POST /api/node/heartbeat` 的响应结构,将 `admin_max_slots` 的值透传给 Worker: ```json { "status": "ok", "admin_max_slots": 4 // 如果无限制,则为 null } ``` *注意*:还需在 `crates/common/src/models.rs` 中为 `NodeInfo` 增加 `pub admin_max_slots: Option` 字段,确保 `/api/admin/nodes` 返回配额数据供 Dashboard 渲染。 ### 3.2 Worker 节点改造 1. **解析心跳响应**: 目前 Worker 的心跳线程(`crates/node/src/worker.rs`)忽略了响应体。需要在 `crates/common/src/models.rs` 中定义 `NodeHeartbeatResponse` 结构体,并在 Worker 中反序列化,提取 `admin_max_slots`。 2. **状态共享**: Worker 并非使用 `Semaphore` 控制并发,而是依赖 `Arc` (`active_slots`) 计数。我们需要在 `NodeWorker` 的状态中新增一个 `effective_max_slots: Arc`,以便心跳线程和领用线程共享配额信息。 3. **动态调整并发控制逻辑**: - 心跳线程在收到心跳响应后,计算生效配额并更新: ```rust let target_slots = match admin_max_slots { Some(admin_limit) => std::cmp::min(admin_limit as usize, physical_max_slots), None => physical_max_slots }; self.effective_max_slots.store(target_slots, Ordering::Release); ``` - 任务领用(Claim)主循环的条件相应修改为: ```rust let active = self.active_slots.load(Ordering::Acquire); if (active as usize) < self.effective_max_slots.load(Ordering::Acquire) { // ... 领用任务 ... } else { tokio::time::sleep(Duration::from_secs(2)).await; } ``` - **平滑降级**:如果缩减时当前正在运行的槽位大于 `target_slots`,上述 `if` 判断会直接失败,Worker 自然进入 `sleep` 分支待命。无需强行中断物理计算任务,等现有任务结束后,数量便会自动回落到新配额以下,实现安全平滑降级(即便配额设为 0,也会安全地暂停接收新任务)。 ### 3.3 前端控制台 (Dashboard) 改造 1. **保持表格原状**: 外部节点列表的大表维持原状(“CPU 槽位”列仍然显示 `占用 / 最大`,不作修改,保持界面整洁)。 2. **重构操作列交互(收敛至统一弹窗)**: - 将“管理操作”列中原有的“启用/停用”、“重发 Token”等多个离散按钮,统一替换为一个**三个点 (More Options) 的 SVG 图标按钮**。 - 点击该按钮后,利用系统中现有的弹窗样式 (如 `.modal-card`) 弹出一个**节点详情与管理 Modal**。 3. **节点详情管理弹窗内容**: - **基础信息展示**:显示节点的详细状态、心跳时间、资源使用率等。 - **槽位占用情况展示**:如果没有配额限制,显示格式为 `16/16`(占用/最大);如果有配额限制,显示格式为 `12/12/16`(占用/配额/最大),其中“最大”代表节点 `.env` 中 `DCTS_MAX_SLOTS` 设置的运行使用上限。 - **修改配额 (CPU 核数)**:提供表单供输入新的配额数字,调用 `/api/admin/nodes/:node_id/quota` 保存;或提供“清除配额”恢复运行使用的最大核数上限。 - **节点调度控制**:提供“暂停(停用)/启动(启用)节点”的操作按钮。 - **安全管理**:提供“重新颁发 Token”的操作按钮。 ## 4. 方案优势 1. **平滑降级**:不中断正在运行的 TLUSTY 任务,实现优雅缩容。 2. **网络高效**:利用现有的高频心跳链路顺带下发配置,不增加额外的 RPC 接口,且避免了服务端阻断 claim 带来的无效轮询。 3. **职责清晰**:Server 依然是状态中心,Worker 仍旧掌控并发调度,逻辑解耦。