22 KiB
ComfyUI Cluster Bridge 优化报告
生成日期:2026-05-12 分析范围:backend / frontend / message-dispatcher / 任务队列后端 四个子项目
一、严重问题(Critical)
1.1 任务状态机断裂 — 死代码导致任务永远无法进入 running 状态
问题:sendTaskToInstance 方法中,catch 块通过 throw new Error(errorMessage) 重新抛出异常,导致 task.status = 'submitted'(L191-L192)作为死代码永远无法被执行。这意味着所有成功提交的任务状态永远是 pending,而非 submitted。进而 handleExecutionStart 中的状态检查(L209-L212)总是失败,任务永远无法进入 running 状态。
影响:核心业务流程断裂,任务的完整生命周期无法正常流转。
1.2 临时文件泄漏 — 磁盘空间耗尽风险
问题:L118-L121 的临时文件清理代码(fs.existsSync + fs.unlinkSync)被整块注释。结合 task-forwarder/index.js 中 uploadImage 方法每次都写入 uploads/ 目录,所有通过 HTTP API 下载的临时文件永远不会被删除。
影响:生产环境运行一段时间后磁盘空间会被耗尽。
1.3 心跳定时器泄漏 — 停止后仍在发送心跳
问题:L37-L39 中 setInterval 创建的定时器引用未存储在实例属性上。调用 stop() 时无法 clearInterval,导致即使 stop 后心跳仍在尝试发送。
影响:关闭连接后产生大量错误日志,资源泄漏。
1.4 Token 刷新失败时请求永久挂起
文件:request.js
问题:L76-L79 中 token 刷新失败后,refreshSubscribers 中的回调永远不会被执行(既不 resolve 也不 reject),导致堆积在队列中的请求 Promise 永久挂起,造成内存泄漏。
影响:用户体验卡死,内存持续增长。
1.5 废弃 API 导致实例增删改功能永远失败
文件:api/index.js
问题:addInstance、updateInstance、deleteInstance(L220-L233)被标记为废弃并始终 Promise.reject,但 Config.vue 和 Instances.vue 仍在调用它们。
影响:用户看到的添加/编辑/删除操作永远提示失败,属于功能缺陷。
1.6 app.js 与 webSocket.js 严重代码重复
文件:app.js / webSocket.js
问题:两个文件(各 370+ 行)的 WebSocket 逻辑、Worker 线程创建、sendMessageToClient、createWebSocketServer、gracefulShutdown 等功能超过 90% 完全相同。如果同时启动会导致端口冲突。
影响:维护成本翻倍,修改时极易遗漏,已存在多处逻辑不一致。
1.7 Worker 线程崩溃后无重启机制
文件:app.js / webSocket.js
问题:Worker 线程的 exit 事件仅记录日志(L42-L49),没有任何重启逻辑。
影响:Worker 线程意外崩溃后,该线程负责的整个任务处理管道将永久停滞。
1.8 socketMap 内存泄漏
文件:app.js / webSocket.js
问题:socket.on('close') 处理中(L280-L284)只清除定时器并记录日志,但未从 socketMap 中删除已关闭的连接。
影响:长时间运行后 socketMap 持续增长,内存泄漏。
1.9 服务器重启直接丢弃等待队列
文件:initQueue.js
问题:L95-L98 在检测到 existingInfo 存在时,直接 redis.del(waitName) 删除所有等待队列数据,没有尝试将任务恢复或迁移。
影响:服务器重启会导致所有等待中的任务永久丢失。
1.10 回调接口先发 200 再异步处理
文件:callback.js
问题:L7 先向客户端发送 200 成功响应,再异步处理数据。如果后续处理失败,客户端已收到成功响应,无法感知。
影响:数据完整性风险,错误无法通知上游系统。
1.11 配置文件加载无异常保护
文件:Config.js
问题:fs.readFileSync + JSON.parse 未包裹 try-catch。如果 model.json、Platform.json 或 cost.json 损坏或丢失,整个进程直接崩溃。
影响:配置文件损坏导致服务不可用,毫无恢复能力。
1.12 容量配置的 falsy 值判断错误
问题:L6 使用 parseInt(...) || 10,当 EXTERNAL_CAPACITY_MAX=0 时(意为"禁止外部任务"),0 是 falsy 值,会错误回退到默认值 10。应使用 ?? 或显式 isNaN 检查。
影响:配置语义被曲解,无法正确禁用外部任务。
1.13 Config.vue 与 Instances.vue 100% 代码重复
文件:Config.vue / Instances.vue
问题:两个文件内容完全一致。路由标题是"配置管理",但显示的是实例管理页面。显然是复制粘贴后忘记修改。
影响:语义混乱,无法区分配置管理和实例管理功能。
1.14 登录页明文展示默认账号密码
文件:Login.vue
问题:L54 在模板底部直接展示"默认账号: admin / admin123",且表单初始值(L73-L76)硬编码了这些凭据。
影响:严重安全隐患,即使是内部管理系统也不应在 UI 明文展示凭据。
1.15 监控页面 ECharts 全量导入 + 假数据
文件:Monitor.vue
问题:
- L102:
import * as echarts from 'echarts'全量导入,增加约 1MB 未压缩体积 - L126-L135:趋势图使用硬编码随机模拟数据,无实际业务价值
- L389-L398:首次加载时先刷新数据再初始化图表,导致首次渲染显示全 0 数据
影响:Bundle 体积暴增,监控数据无实际意义。
1.16 task-scheduler 超时检查定时器无法清除
问题:L385-L389 的 startTimeoutCheck() 中 setInterval 返回值未存储,导致 shutdown 时无法清除,定时器回调在服务停止后仍可能执行。
1.17 Redis 多连接实例浪费
文件:jwt.js / rate-limit.js
问题:两个文件各自创建独立的 new Redis() 实例连接到同一个 Redis 服务器,浪费 TCP 连接资源。应抽取共享 Redis 单例模块。
1.18 速率限制的竞态条件
问题:L28-L41 使用 exists + incr/setex 模式,存在竞态条件。两个并发请求同时到达且 key 不存在时,第二次 setex 会覆盖第一次的计数器。应改为 incr + 条件 expire 模式。
1.19 密码验证的时序竞态
文件:routes.js
问题:L15-L23 通过顶层 IIFE 异步计算 hashedAdminPassword。在该 Promise 完成之前到达的登录请求会退化为明文比较(L46),造成短期安全窗口。
1.20 WebSocket Server 任务发送失败未回调调度器
问题:sendTaskToInstance 立即失败路径(L299-L301)释放了实例锁但未调用 taskScheduler.handleTaskFailure,导致任务在 processingTasks Map 中残留。
二、高优先级问题(High)
2.1 多个模块缺少过期清理机制
| 文件 | 对象 | 说明 |
|---|---|---|
| task-forwarder/index.js | this.tasks Map |
已完成任务永不清理,内存持续增长 |
| workflow-converter/index.js | this.cache Map |
无限增长的工作流缓存 |
建议:添加 TTL 或 LRU 淘汰策略,定期清理过期条目。
2.2 硬编码敏感信息
多处代码中存在硬编码的 IP 地址、Token、凭证等:
| 文件 | 行号 | 内容 |
|---|---|---|
| workflow-converter/index.js | L6 | http://117.72.204.159/AIGC/static/public/workflows |
| file-uploader/index.js | L35-L36 | 内网地址 + Token '123456' |
| task-queue-client/index.js | L43 | wss://www.whjbjm.com/message-dispatcher |
| task-scheduler/index.js | L24-L27 | 队列大小限制硬编码 |
| jwt.js | L4 | JWT_SECRET 默认值 |
建议:所有敏感配置和外部依赖地址应全部通过环境变量配置。
2.3 console.log 调试残留 / 日志不一致
| 文件 | 说明 |
|---|---|
| task-queue-client/index.js L176 | 生产环境残留 console.log |
| websocket-server/index.js | 大量 console.log/warn 混用,未统一使用 winston logger |
2.4 全量注册 Element Plus 图标
文件:main.js L14-L16
问题:for...of 循环将整个图标库(约 300+ 个)全部注册为全局组件,预估增加 ~400KB bundle。实际只使用了约 13 个图标。
建议:删除全局注册循环,改为按需导入。
2.5 refreshToken 命名冲突
文件:user.js
问题:L7 的 state refreshToken 与 L78 的 action refreshToken(由 refreshTokens 别名)同名,导致代码可读性严重下降。
2.6 两个 axios 实例 token 处理逻辑不一致
文件:api/index.js
问题:messageDispatcherRequest 和 request.js 是两个独立 axios 实例,token 拦截逻辑重复实现且行为不一致。
2.7 bridge-manager 锁超时不匹配
问题:LOCK_TIMEOUT 仅 30 秒,但 TASK_TIMEOUT 为 30 分钟。任务确认可能因长耗时而在锁超时之后到达,此时锁已被自动释放。
2.8 schedulePendingTasks 缺乏并发保护
问题:L170-L178 被多个异步路径同时调用(addTask、handleTaskComplete、handleTaskFailure、handleInstanceOffline、setCurrentCapacity、定时器),没有并发保护,极端情况下可能导致任务重复分配或丢失。
2.9 Worker 线程管道配置未统一
文件:initQueue.js
问题:
addPlatformsProcess(L238-L256)中taskCountMap的count值被完全忽略,仅按 Map 条目计数recoverPlatformCounts(L109-L125)计算了pollingCount但从未写回 Redis,实际上没有执行恢复操作reducePlatformsProcessSingle(L260-L285)在整个代码库中从未被调用(死代码)
2.10 部分 fetch 请求无超时设置
文件:generat.js / polling.js / record.js
问题:fetch() 调用未设置超时,外部平台无响应时请求可能挂起极长时间。
三、中优先级问题(Medium)
3.1 缺少优雅关闭处理
文件:index.js(backend) / index.js(任务队列)
问题:没有 SIGTERM/SIGINT 信号处理,进程被 kill 时无法执行清理(关闭 WebSocket 连接、停止健康检查等)。
3.2 健康检查的 setInterval 堆积风险
问题:L52-L55 使用 setInterval 顺序检查实例。如果某次检查耗时超过 interval,任务会堆积。建议改用 setTimeout 递归模式。
3.3 负载均衡未使用实际负载数据
问题:selectInstance 使用简单轮询(Round-Robin),不利用已维护的 load 字段。建议实现加权轮询或最少连接算法。
3.4 工作流缓存的深拷贝效率
文件:workflow-converter/index.js
问题:多处使用 JSON.parse(JSON.stringify(...)) 做深拷贝,对于大型工作流效率较低。可考虑使用 structuredClone(Node 17+ 支持)。
3.5 前端路由缺少 404 页面
问题:没有通配 404 路由,未匹配路径显示空白页面。
3.6 实例管理端口号计算不可靠
文件:Instances.vue L147
问题:openAddDialog 中端口号计算 8001 + instances.value.length,如果有删除操作,端口号可能重复。
3.7 健康检查后不必要的全量刷新
文件:instance.js L33-L58
问题:每次健康检查操作成功后调用 fetchInstances() 重新拉取全部实例,建议仅更新被检查实例的状态。
3.8 任务列表缺少分页
问题:fetchTasks 一次性拉取所有任务,无 page/limit 参数。数据量大时会造成性能问题。
3.9 移动端响应式适配缺失
问题:侧边栏仅支持折叠模式,无移动端的抽屉式导航适配。
3.10 垃圾回收 / 清理脚本分散
项目中存在多个一次性清理脚本(clear_coze_tokens.js、clear_old_platforms.js、reset_pqtasks.js、checkQueue.js、check_redis.js),缺乏统一的运维工具入口。
3.11 mdWebSocketServer 能力状态 external 永远为 0
问题:L105-L106 只更新了 this.currentCapacity.internal,而 external 从未更新,始终为 0。
3.12 消息 key 并发冲突风险
问题:L33 使用 Date.now() 生成 messageKey,同一毫秒内多消息会导致 key 冲突,消息丢失。
3.13 redis.keys() 应改为 SCAN
文件:queueRecovery.js L112
问题:redis.keys() 是 O(N) 阻塞操作,生产环境应使用 SCAN 游标方式遍历。
3.14 capacityGuard 使用忙等待自旋锁
问题:L7-L12 使用 setTimeout(resolve, 10) 实现忙等待,高并发时产生大量定时器。建议改用 Promise 队列模式。
3.15 前端缺少全局错误处理器
文件:main.js
问题:没有 app.config.errorHandler 设置,未捕获错误不会上报或友好提示。
四、架构层面建议
4.1 项目结构
| 问题 | 建议 |
|---|---|
| 根 package.json 的依赖与实际使用的子项目依赖不一致 | 使用 pnpm workspace 或 nx/turborepo 统一管理 monorepo |
任务队列后端目录名含中文(任务队列后端) |
改为英文目录名,避免跨平台兼容问题 |
| app.js 和 webSocket.js 重复 | 提取公共逻辑到共享模块 |
| .env 文件中含敏感信息(Redis 密码、JWT Secret 等) | 从代码仓库中移除(如果已提交,需轮换密钥并清理历史) |
4.2 统一日志管理
当前三个子项目(backend、message-dispatcher、任务队列后端)都使用了 winston,但:
- 部分模块仍使用
console.log - 日志级别和格式未统一
- 缺少结构化日志(JSON 格式)支持,不利于日志聚合分析
建议:提取共享 logger 包,统一日志格式,支持生产环境 JSON 输出。
4.3 统一错误处理
各子项目 HTTP API 的错误响应格式不统一,建议定义统一的错误响应结构(如 { code, message, data }),并在中间件层面统一处理。
4.4 环境变量管理
多个 .env 文件中存在重复定义(如 REDIS_HOST、JWT_SECRET),部分配置优先从 config 文件读取,部分从环境变量读取,读取优先级不统一。建议:
- 使用单一配置加载策略
- 环境变量优先级始终高于配置文件
- 文档化所有配置项
4.5 测试覆盖
当前项目没有明显的测试文件(根 package.json 的 vitest 仅声明未使用),建议为核心模块添加单元测试,特别是:
- task-forwarder 的任务状态机
- task-scheduler 的调度逻辑
- rate-limit 的并发安全性
五、优先级排序(按修复建议)
| 优先级 | 编号 | 问题 | 预估工时 |
|---|---|---|---|
| P0 | 1.1 | 任务状态机断裂(死代码) | 小 |
| P0 | 1.2 | 临时文件泄漏 | 小 |
| P0 | 1.3 | 心跳定时器泄漏 | 小 |
| P0 | 1.4 | Token 刷新失败请求挂起 | 小 |
| P0 | 1.5 | 废弃 API 导致功能失败 | 小 |
| P0 | 1.11 | 配置文件无异常保护 | 小 |
| P0 | 1.12 | 容量配置 falsy 判断错误 | 小 |
| P0 | 1.15 | 监控页面假数据 | 中 |
| P0 | 1.14 | 登录页明文凭据 | 小 |
| P1 | 1.6 | app.js/webSocket.js 重复 | 大 |
| P1 | 1.7 | Worker 无重启机制 | 中 |
| P1 | 1.8 | socketMap 内存泄漏 | 小 |
| P1 | 1.9 | 重启丢弃队列 | 中 |
| P1 | 1.10 | 回调先 200 再处理 | 中 |
| P1 | 1.13 | Config/Instances 重复 | 大 |
| P1 | 1.16-1.20 | message-dispatcher 问题集 | 中 |
| P1 | 2.1 | 内存泄漏(Map 无清理) | 中 |
| P1 | 2.4 | Element Plus 图标全量导入 | 小 |
| P2 | 2.2-2.10 | 高优改进项 | 中 |
| P3 | 3.1-3.15 | 中优改进项 | 中-大 |
报告结束。以上问题均仅作分析记录,未对代码进行任何修改。