Skip to content

IEC61850 超大模型发现性能、超时与服务状态治理

版本:1.0
日期:2026-07-05
分类:模型发现 / 性能优化 / 超时治理 / 前后端稳定性 / Bug 修复
状态:已实施

1. 问题背景

IEC61850 客户端发现超大模型时,现场出现了以下相互关联的问题:

现象直接影响
模型发现速度随模型规模显著下降数千个 DO、DA 模型需要等待较长时间
发现进度长时间停留在同一百分比用户无法区分“正在处理单个大 LD”和“任务已经卡死”
个别情况下任务长时间无法结束后续发现任务被活动任务状态拒绝
普通接口在模型处理期间触发 5 秒超时页面状态刷新、表格查询和进度查询失败
左下角服务状态显示异常后端繁忙被误判为后端进程不可用
模型表格翻页和定时刷新开销偏高每次请求都构造整棵模型树,造成 CPU 和内存抖动

本次整改不只是延长前端超时时间,而是分别治理协议请求、事件循环、模型算法、缓存、分页和前端轮询。

2. 根因分析

2.1 MMS 请求数量随模型规模放大

在线发现需要遍历 LD、LN、DO、DA 和 BDA。旧实现还会为大量 DA 依次执行:

  1. 变量规格查询;
  2. 运行时值读取回退;
  3. DO 描述 dU/d 的多 FC 兼容读取。

已有运行日志中的一个模型包含 25 个 LD、4,566 个 DO 和 18,252 个 DA,其中 17,224 个 DA 进入运行时类型探测。在远端网络或设备响应不稳定时,单次请求延迟会被数万次调用放大。

2.2 只设置连接超时,未显式设置请求超时

连接建立和 MMS 服务请求使用不同的超时策略。旧实现只设置 IedConnection_setConnectTimeout(),后续目录查询、类型探测和读值依赖底层库默认请求超时。某个节点无响应时,发现任务可能在一次同步调用上停留数秒,并在大量节点上重复发生。

2.3 长任务仍占用 ASGI 事件循环

部分 FastAPI async 路由直接执行同步工作,包括:

  • ICD/SCL 解析;
  • 模型加载和设备重建;
  • IEC61850 服务端模型启动;
  • 大型模型树构造。

后端只有一个 Uvicorn 工作进程。同步工作占用事件循环时,/api/health 和普通业务接口无法及时获得调度,因此出现接口超时和服务状态误判。

2.4 前端轮询产生请求叠加

设备页面同时存在模型进度、设备状态和表格刷新等周期任务。旧表格刷新和设备状态轮询没有统一的 single-flight 保护;当前一轮请求尚未结束时,下一轮仍可能继续发起。后端繁忙时会形成排队和超时的正反馈。

2.5 分页发生在完整模型构造之后

树接口虽然只向前端返回一页数据,但旧实现先把全部 DO/DA/BDA 转换成嵌套字典,再切出当前页。模型越大,每秒自动刷新产生的无效对象分配越明显。

2.6 磁盘缓存读取后重复持久化

ModelCache.get() 从磁盘恢复模型后调用完整的 set(),导致已经存在的 JSON 缓存再次序列化和覆盖。对于约 19MB 的模型缓存,这会额外增加加载延迟与磁盘写放大。

3. 后端整改

3.1 不可变超时策略

新增 Iec61850Timeouts 不可变数据类,统一管理每个 MMS association 的超时:

配置默认值环境变量
连接超时3,000msEMS_IEC61850_CONNECT_TIMEOUT_MS
请求超时3,000msEMS_IEC61850_REQUEST_TIMEOUT_MS
模型发现任务上限600sEMS_IEC61850_DISCOVERY_TIMEOUT_SECONDS

连接创建后会同时调用:

python
iec61850.IedConnection_setConnectTimeout(connection, timeouts.connect_ms)
iec61850.IedConnection_setRequestTimeout(connection, timeouts.request_ms)

环境变量非法、为空或非正数时回退到安全默认值,不影响应用启动。

3.2 阻塞工作移出事件循环

使用 asyncio.to_thread() 隔离以下同步工作:

  • ICD 预览和统一 SCL 导入;
  • GOOSE 文件解析回退;
  • IEC61850 模型加载;
  • 设备构造与 ICD 模型注入;
  • IEC61850 服务端启动和停止。

路由仍保持原有请求和响应结构,前端 API 无需迁移。

3.3 发现任务期限和细粒度进度

发现任务增加总期限检查。超时异常作为任务控制流向上传递,不会被节点级“跳过错误”逻辑吞掉。

进度由原来的 LD 级更新细化为:

  • LD 内各 LN 的处理进度;
  • LN 内每个 DO 的发现进度;
  • 每个 DO 描述的读取进度;
  • 模型构建、资源整理和描述读取阶段。

即使单个 LD 包含大量 DO,进度百分比和消息也会持续变化。

3.4 变量规格探测熔断

部分 IED 暴露变量规格 API,但对所有变量规格请求都返回失败。发现服务增加任务级熔断器:连续 32 次规格探测失败后,本次任务停止继续发送规格查询,直接使用运行时读值或静态类型推断。

该策略避免对超大模型中的每个 DA 都额外执行一次确定会失败的 MMS 请求;下一次发现任务会重新初始化熔断状态,不会永久降级。

3.5 缓存内存层与持久化层分离

ModelCache 新增仅写入内存 LRU 的 _remember() 路径:

  • set():更新内存并持久化文件;
  • get() 磁盘恢复:只更新内存,不重复写文件。

这样既保留 LRU 行为,也避免读取缓存时无意义地覆盖原文件。

3.6 健康状态区分繁忙与故障

/api/health 保持 HTTP 200 的存活语义,同时增加:

json
{
  "status": "busy",
  "busy": true,
  "active_operations": [
    {
      "device": "IED1",
      "operation": "discover",
      "elapsed_seconds": 12
    }
  ]
}

模型发现、连接或批读属于健康但繁忙的服务状态,不再与进程不可用混为一谈。

4. 前端整改

4.1 令牌式自动刷新门闩

新增独立的 autoRefreshGate。长任务开始时获取暂停令牌,结束或组件卸载时执行幂等释放:

ts
const release = acquireAutoRefreshPause("iec61850-model-discovery")
try {
  await discoverModel()
} finally {
  release()
}

多个任务重叠时,只有全部令牌释放后才恢复表格轮询,避免一个任务提前结束而错误恢复其他任务暂停的轮询。

4.2 轮询 single-flight

设备状态和表格刷新增加请求进行中标志。前一轮请求未完成时跳过新的定时触发,不再堆积并发请求。

4.3 服务状态防抖

左下角状态栏只有在连续 3 次健康检查失败后才显示后端异常;任意一次成功都会立即清零失败计数。短暂的调度延迟不会再造成状态闪红,真实进程退出仍能在有限时间内被识别。

4.4 长任务使用独立超时

普通 API 继续使用 5 秒超时,文件操作继续使用 60 秒超时;模型发现单独使用 610 秒前端预算,与后端默认 600 秒任务上限对应,并预留响应编码和网络传输时间。

5. 模型树分页优化

模型树构造器新增 offsetlimit 参数。处理流程调整为:

  1. 扫描模型并计算符合筛选条件的总数;
  2. 跳过当前页之前的节点;
  3. 只为当前页构造 DA/BDA 嵌套对象;
  4. 返回当前页数据和完整 total

同时建立 DO 到测点名称的索引,避免搜索时对每个 DO 再全量扫描全部测点。

本地同一模型诊断结果如下:

指标调整前调整后
返回页大小1010
可见 DO 总数2,3742,374
树构造耗时约 0.299s约 0.004s
Python 临时内存峰值约 9.5MB约 0.03MB

该数据为本机离线样本,实际收益会随模型规模、筛选条件和硬件变化。

6. 主要变更文件

文件变更摘要
src/proto/iec61850/core/connection.py不可变超时策略,显式设置 MMS 请求超时
src/proto/iec61850/model/discovery.py探测熔断、DO 级进度、任务期限传播
src/proto/iec61850/iec61850_client.py描述读取细粒度进度
src/proto/iec61850/model/cache.py内存恢复与磁盘持久化路径分离
src/device/protocol/iec61850_handler.py发现总期限、服务端启停线程隔离
src/web/api/device/router.py模型导入和加载线程隔离
src/web/api/channel/helpers.py设备重建线程隔离
src/web/api/channel/import_points.pySCL/ICD/GOOSE 解析与服务启动线程隔离
src/web/api/channel/iec61850.py模型树前置分页与搜索索引
src/web/app.py健康接口暴露 busy 和活动任务
front/src/composables/autoRefreshGate.ts令牌式轮询暂停门闩
front/src/composables/useAutoRead.ts表格轮询 single-flight
front/src/views/Device.vue模型任务暂停轮询、独立发现超时、状态请求防重入
front/src/components/layout/SideBarStatus.vue连续失败阈值与状态防抖

7. 验证结果

验证项结果
Ruff 格式与静态检查通过
IEC61850 非现场依赖单元测试173 项通过
本次定向后端回归31 项通过
前端 Jest9 项通过
Vue TypeScript 检查通过
Vite 生产构建通过
缓存文件读取后修改时间保持不变

test_read.py 中依赖现场 MMS 模型的用例未纳入上述 173 项统计;诊断环境中的 IED 模型与测试预设不一致,目标对象返回 object-non-existent,不属于本次代码回归。

8. 运维建议

  1. 优先通过环境变量调整 MMS 请求超时,不要修改全局前端 5 秒超时掩盖后端阻塞。
  2. 跨地域或高延迟网络可适当提高 EMS_IEC61850_REQUEST_TIMEOUT_MS,但应同时评估模型节点数量。
  3. 发现任务长时间处于 busy 时,可通过健康接口中的设备、操作类型和已运行秒数定位任务。
  4. 超大模型首次在线发现完成后优先使用本地模型缓存,避免重复执行完整 MMS 遍历。
  5. 现场验收应覆盖弱网络、部分节点访问错误、单个超大 LD、连续重新发现和发现期间浏览其他页面等场景。

Released under the Apache 2.0 License.