【MES系统】MES为什么需要SSE?从设备实时监控谈Spring Boot流式推送设计

很多程序员第一次开发 MES(Manufacturing Execution System,制造执行系统)时,都会遇到这样一个需求:

如何让设备状态实时刷新?

看起来,这只是一个很小的功能。

但真正做过 MES 的人都知道,它背后涉及的不仅仅是前端刷新,而是整个系统的数据推送架构。

今天,我们就从一个真实的 MES 场景出发,聊聊为什么越来越多的工业系统开始采用 SSE(Server-Sent Events),以及如何在 Spring Boot 中实现一个高可靠的流式推送架构。


一、MES中的一个真实场景

假设现在有一个离散制造车间。

车间内有 30 台设备正在加工。

MES 首页的大屏需要实时展示:

text 复制代码
设备A:运行中

设备B:待料

设备C:报警

设备D:加工完成

与此同时,还需要实时刷新:

  • 当前工单执行进度
  • 实时产量
  • 设备利用率(OEE)
  • 告警信息
  • 操作员状态

如果采用传统 HTTP 请求,最容易想到的方案就是:

text 复制代码
浏览器
        │
每5秒请求一次
        │
Spring Boot
        │
查询数据库
        │
返回最新状态

刚开始看起来没有问题。

但是随着项目规模扩大,很快问题就出现了。


二、轮询为什么越来越慢?

假设:

一个车间有:

  • 100 个在线客户端
  • 每个客户端打开 3 个实时页面
  • 每 2 秒刷新一次

那么服务器每分钟需要处理:

text 复制代码
100 × 3 × 30 = 9000 次请求

更关键的是:

这 9000 次请求中,大部分返回的数据都是:

text 复制代码
没有变化。

也就是说:

浏览器不停地问服务器:

"设备状态变了吗?"

服务器一次次回答:

"没有。"

这种通信方式显然非常低效。

而真正发生变化的,也许只是:

text 复制代码
09:21:15

设备A:

RUNNING

↓

FAULT

整个系统真正需要推送的数据,其实只有这一条。


三、真正需要的是"服务器主动通知"

MES 更希望采用这样的通信方式:

text 复制代码
设备状态变化
        │
MES收到事件
        │
立即通知浏览器
        │
浏览器更新页面

也就是说:

不是浏览器不停询问。

而是:

服务器有变化再通知客户端。

这就是 SSE 的核心思想。


四、什么是SSE?

SSE(Server-Sent Events)可以理解为:

服务器主动向浏览器持续发送数据。

整个通信过程只有一次 HTTP 请求。

连接建立后:

浏览器一直保持连接。

服务器只要有新的数据:

立即写入连接。

例如:

text 复制代码
Browser
        │
──────────────►
Spring Boot

连接保持不断开

Spring Boot

write()

flush()

write()

flush()

Browser

不断收到数据

整个过程中:

浏览器无需再次发起请求。


五、为什么MES特别适合SSE?

MES 有一个非常典型的特点:

数据变化频繁,但客户端几乎不需要向服务器发送实时消息。

例如:

设备状态:

text 复制代码
IDLE

↓

RUNNING

↓

FAULT

↓

RUNNING

工单状态:

text 复制代码
CREATED

↓

RELEASED

↓

RUNNING

↓

COMPLETED

生产数量:

text 复制代码
356

↓

357

↓

358

↓

359

这些数据:

全部都是:

服务器主动产生。

浏览器只是:

接收。

因此:

SSE 与 MES 的业务模型天然匹配。


六、为什么不是WebSocket?

很多人第一反应:

实时通信就应该使用 WebSocket。

其实未必。

对于 MES 来说:

浏览器主要承担:

text 复制代码
数据显示

真正的数据流:

几乎都是:

text 复制代码
设备

↓

MES

↓

浏览器

浏览器极少需要:

持续向服务器发送消息。

因此:

SSE 已经完全能够满足需求。

相比 WebSocket:

SSE:

  • 基于 HTTP
  • 浏览器原生支持
  • 自动重连
  • 部署简单
  • 更容易经过 Nginx、网关和负载均衡

对于:

  • 实时看板
  • 工单监控
  • OEE监控
  • 告警推送

都是非常合适的选择。


七、Spring Boot如何实现SSE?

Spring MVC 已经提供了现成的支持:

java 复制代码
@GetMapping("/device/stream")
public SseEmitter stream() {
    SseEmitter emitter = new SseEmitter(0L);
    emitterMap.put(userId, emitter);
    return emitter;
}

其中:

java 复制代码
new SseEmitter(0L)

表示:

text 复制代码
永不超时

随后:

当设备状态变化时:

java 复制代码
emitter.send(
    SseEmitter.event()
        .name("device")
        .data(deviceStatus)
);

浏览器立即收到:

javascript 复制代码
eventSource.onmessage = function(event){
    console.log(event.data);
}

整个过程无需再次建立连接。


八、真正的实时推送不是"查询数据库"

各位一定要避免一个误区,就是不停地查询数据库来实现"实时"的效果,这样对资源是极大的浪费

很多项目都会这样写:

text 复制代码
设备变化

↓

更新数据库

↓

浏览器轮询数据库

实际上:

MES 完全没有必要这样设计。

更合理的方式应该是:

text 复制代码
PLC

↓

设备采集服务

↓

发布设备事件

↓

MES状态机

↓

SSE推送

↓

浏览器

设备事件一旦产生:

立即推送。

数据库只是:用于持久化。

而不是:用于实时通信。


九、生产环境需要解决哪些问题?

真正上线后,仅仅使用 SseEmitter 还远远不够。

通常还需要考虑以下几个方面。

1. 连接管理

每一个浏览器都会对应一个 SseEmitter

需要统一维护连接池。

例如:

text 复制代码
ConcurrentHashMap<UserId, SseEmitter>

避免连接丢失。


2. 心跳机制

很多网关会自动断开长连接。

因此:

服务器需要定时发送:

text 复制代码
heartbeat

保持连接存活。


3. 断线清理

浏览器关闭后:

必须及时:

java 复制代码
removeEmitter()

否则:

连接越来越多。

最终导致:

内存泄漏。


4. 自动重连

浏览器网络异常:

SSE 可以自动重新建立连接。

这也是它相比普通 HTTP 更大的优势。


5. 异步推送

设备状态变化:

绝不能阻塞 PLC 数据采集线程。

通常采用:

text 复制代码
设备事件

↓

消息队列

↓

推送线程池

↓

SSE

这样即使:

1000 个客户端在线。

设备采集仍然不会受到影响。


十、MES实时推送的推荐架构

实际项目中,我们更推荐采用事件驱动架构。

text 复制代码
PLC
        │
        ▼
设备采集服务
        │
        ▼
设备事件(Event)
        │
        ▼
MES业务状态机
        │
        ▼
事件总线
        │
        ▼
SSE推送服务
        │
        ▼
浏览器

这里:

SSE 只是:

最后一公里。

真正核心的是:

整个系统采用:

事件驱动。

设备状态变化。

工单状态变化。

告警产生。

生产完成。

都应该抽象成:

统一事件。

这样:

不仅可以推送浏览器。

未来:

消息队列、AI分析、数据湖、BI平台,都可以复用同一套事件流。


十一、结语

很多程序员学习 SSE,关注的是:

Spring Boot 如何使用 SseEmitter

但对于工业软件来说:

真正的问题并不是:

如何推送数据。

而是:

什么时候推?
推什么?
由谁触发推送?

只有把设备事件、MES 状态机和实时推送结合起来,

SSE 才真正成为制造执行系统中的一部分,而不是一个孤立的通信技术。

对于 MES 来说,实时推送的价值,从来不是让页面"动起来"。

而是让设备状态、工单执行和生产过程,以最低延迟、最高可靠性的方式,第一时间传递给真正需要它的人。

相关推荐
Lhappy嘻嘻1 小时前
网络初识|基础入门:局域网广域网、IP 端口、TCP/IP 五层模型与封装解封装全过程
java·开发语言·网络·笔记·网络协议·tcp/ip
卷无止境1 小时前
Guardrails.ai:为大语言模型加一道"安全阀"
后端·python
Java面试题总结1 小时前
区间树:反向映射的前置数据结构
java·前端·数据结构
尚早立志1 小时前
六)Spring Boot 源码研读之prepareContext 准备上下文
java·spring boot·后端·spring
Dovis(誓平步青云)2 小时前
《Linux CPU频率为何忽高忽低:cpufreq、idle状态与性能抖动教程》
linux·运维·服务器·spring boot·后端·生成对抗网络
Lhappy嘻嘻9 小时前
Java IO|File 文件操作 + 字节流 / 字符流完整笔记 + 递归删除文件实战
java·笔记·php
To_OC9 小时前
手写 AI 编程 Agent 的命令执行工具:我被 child_process 坑出来的实战经验
后端·node.js·agent
伊玛目的门徒10 小时前
试用leetcode之典中典 二数之和问题
java·算法·leetcode
逝水无殇12 小时前
C# 异常处理详解
开发语言·后端·c#