2025 年 2 月,我从一个空目录开始搭设备状态矩阵 APP。第一次提交很大:命令入口、配置、路由、模型、SQLite、TDengine、WebSocket、缓存、分页和矩阵计算都在里面。
这种“大提交”现在看并不理想,但它保留了当时一个很真实的认知过程:我一开始以为自己在做实时推送,后来发现其实是在做一个小型读模型。
页面上的一个格子从哪里来
矩阵的一行可能代表设备,一列可能代表业务指标。格子的颜色却不只取决于最新采集值,还可能受以下信息影响:
| 信息 | 来源 | 变化频率 |
|---|---|---|
| 设备与模型关系 | 关系库/配置 | 低 |
| 最新指标值 | 时序库/MQTT | 高 |
| 基线计算 | 分析任务 | 中 |
| 未处置告警 | 告警库 | 中 |
| 用户权限 | 权限服务/缓存 | 低但敏感 |
| 在线状态 | 心跳与采集时间 | 高 |
把这些数据在每次 WebSocket 推送前全部查询一遍,功能可以完成,代价是推送越频繁,数据库越忙。
“当前状态”其实是一份物化视图
后来我开始把矩阵看成事件驱动的当前视图:
- 采集值到达,更新指标状态;
- 告警产生,提升对应设备或指标等级;
- 告警处置,重新计算展示状态;
- 权限变化,重新过滤用户可见范围;
- 设备离线,根据最后采集时间改变状态。
WebSocket 不负责计算所有事实,它只负责把已经聚合好的视图推给客户端。这种职责划分能让实时链路更清楚,也方便定位到底是“状态算错了”还是“消息没有推到”。
WebSocket 自己也会制造问题
连接数量增加后,需要处理的事情包括:
- 心跳和读写超时;
- 浏览器异常退出后的连接清理;
- 同一连接并发写导致的数据帧问题;
- 慢客户端队列不断积压;
- 服务关闭时连接和后台任务如何退出。
权限尤其容易被忽略。如果只在握手时检查一次,用户角色变化后,旧连接仍可能收到不应看到的数据。我们后来为矩阵补了权限适配,也经历过因为联调临时关闭、再重新开启权限的反复。这说明权限不是外围功能,它应该进入实时视图的生成过程。
几个月后的回看
后续提交里又陆续修了告警等级映射、时间戳、基线告警不展示、时间转换和定时刷新性能。它们不是互不相关的小 Bug,而是在不断校正“当前状态”的定义。
实时系统最难的并不是毫秒级推送。真正困难的是:当数据来自不同时间、不同存储和不同业务模块时,系统是否能解释页面为什么呈现这个状态。