车间里的实时性麻烦,做MES的都懂。工位上报一个不良品、线边库缺料报警、设备突然停机,这些数据什么时候到,往往不取决于现场发生了什么,而取决于系统什么时候去问。很多MES项目的痛点根本不是功能不够,而是业务都被塞进了“请求-响应”的壳子里,明明现场是一连串连续事件,系统却在用定时器一下一下地捞。这就是为什么我越来越倾向于用事件封装的方式来做MES生产作业业务,把业务动作封装成事件,把组件改造成事件驱动,让系统跟着车间现场的真实节奏走,而不是让现场等系统轮询。这篇就专门聊聊这种组件设计思路,怎么拆、怎么定义事件、怎么让组件消费事件后自己演进状态,以及中间有哪些坑。
1. MES现场的实时性压力:为什么制造业务在请求-响应模式里喘不过气
很多MES项目是从ERP的思路延伸过来的,习惯性地把所有交互设计成“你给我个工单号,我返回工单状态”。单看一个接口没问题,但把几十条产线、几百个工位、上千台设备都接进来之后,问题就慢慢浮出来了。我想先把这个问题说透,因为不清楚为什么要做事件化改造,后面的组件设计很容易变成为了时髦而堆技术。
1.1 车间层的数据流本质:制造现场天然在发事件
车间现场跟ERP的业务模型完全是两种节奏。ERP里的领料、报工、入库,绝大多数是业务员坐在电脑前一条一条录入的,天然就是“人发起、系统响应”的同步模型。但MES对接的是物理世界,是加工中心在切削、AGV在跑、拧紧枪在打螺丝、扫码枪在扫条码。这些动作每时每刻都在发生,每秒钟都会有数据产生,而且没人在中间“点保存”。
拿一条机加工线来说,一个工件过珩磨机,设备要上报加工开始、加工结束、测量结果、是否合格这一串东西;工件流转到下一道工序,系统又得知道它什么时候到、在哪个工位停留了多久。这些本质上都是事件,是一个接一个“已经发生了什么”的事实。你没法用“拉”的模式去处理它,唯一合理的办法是让设备把“发生了什么”主动推出来,让系统被动接收后去更新状态。
我见过不少MES团队明明现场上了PLC采集、上了RFID,结果数据链路还是旧的:上位机把数据写进中间表,MES每5秒扫一次。中间表越堆越厚,数据库CPU越来越高,现场的实时性却一点没体现出来。这就是典型的用“拉”的姿势处理“推”的业务,两边都别扭。
1.2 同步轮询的成本:产线被系统节奏绑架
如果现场已经上了自动化设备,轮询模式的问题会非常具体。第一,系统的实时性被轮询周期锁死。你设5秒轮询,那从设备发出信号到系统感知,平均延迟就是2.5秒,最差5秒。单看5秒好像无所谓,可一个大型MES有几十个轮询任务,任务多了互相争抢数据库连接,延迟就变成十几秒、几十秒,现场等着系统放行的情况就会发生。
第二,轮询会制造大量无效查询。设备已经停了3分钟,但轮询任务不知道,它只知道自己每5秒要查一次停机状态。哪怕是深夜停产时段,只要任务没停,数据库还是被一遍一遍地扫。我在一个项目里见过最夸张的,空跑状态下一张30万行的异常记录表,某个轮询任务里一条没带索引的查询,直接把生产库拖垮了。后来查原因,就是当初写的时候图省事,把所有状态都放在一张表里来回扫。
第三,也是更隐蔽的,轮询让业务逻辑散落在各处的定时器里。今天上了个新需求“缺料超过15分钟要通知班组长”,你猜猜会怎么实现?大概率是在某个服务里加一个定时任务。等需求变成“缺料超过15分钟但线体属于柔性混线模式时通知计划员”,你就得再去翻那个定时任务的代码。业务规则的复杂度根本不是定时任务能承载的,它需要的是事件被触发后走一整套触发规则,而这套东西在轮询模式下根本长不出来。
1.3 一个工单流转的对比:轮询、定时批处理与事件驱动
把抽象问题放到一个具体业务上可能更好理解。假设产线上有一个工位,员工扫一下工单条码开始作业,做完第一道工序,工件流转出去。我们来看看三种不同实现下,这个“扫条码开工”到底是怎么被系统感知的。
轮询模式下,前端扫描后先把记录写进表里,后台任务每隔几秒扫一次这张表,发现新记录就回去更新工单状态、绑定人员、启停工时。这套设计看着简单,但一旦业务要求“开工即锁定工艺版本”,或者“如果该物料有质量冻结标记就禁止开工”,就麻烦了。你得在轮询任务里串行处理大量校验逻辑,处理不完,后面的记录就堆着,现场就感觉“我扫了条码怎么没反应”。
定时批处理模式更粗糙一点,通常是白班夜班交接时批量跑一次报工,上线初期数据不实时也还能忍。但等业务方要求做“产线在制品实时分布图”时,这模式就彻底废了。一天只有两个数据点的系统,怎么画实时分布?
事件驱动模式下,扫条码这个动作本身直接产生一个ScannedStartedEvent,这个事件包含了工单号、工位编码、操作工、时间戳。任何关心这个事件的组件——工单进度组件、工时统计组件、防错校验组件——都订阅它。前端扫完码,事件发出,组件各自更新状态,整个过程是并行的,数据库不会成为瓶颈,业务也不会被定时器绑架。车间里“下一秒就变”的动态,自然会以事件的形式推动系统流转。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 事件封装的第一步:通道划分与消息契约怎么定
确定要采用事件驱动架构之后,第一个实际问题是:事件到底怎么定义,通道怎么切,消息体长什么样。这一步其实决定了整个组件体系的骨架。我见过不少团队一上来就搞一个大而全的事件类,什么字段都往里塞,结果消费者无法判断哪些字段可信,最后所有组件都还是去调接口查数据,事件总线形同虚设。
2.1 事件通道按业务域切,而不是按系统模块切
很多初做事件驱动的人会用系统模块来分通道:报工一个通道、检验一个通道、设备一个通道。这样分是省事了,但随着业务发展会非常拧巴:一个“质量拦截”事件,质量模块要听,生产模块也要听,计划模块搞不好还要听。如果你按模块发,就得往三个通道各发一遍,消息数据靠拷贝来扩散,版本一旦不能同步就全乱套。
我的习惯是严格按业务域划分通道。制造执行域里,我一般分成这几个主要业务域:生产执行域(工单下达、开工、完工、报工)、物料域(叫料、发料、退料、消耗确认)、质量域(检验任务发起、合格判定、不合格处置)、资源域里面的设备状态域(设备故障、维修完成、点检异常)和人员域(上岗、离岗、技能变更)。事件跟着业务事实走,发到对应业务域的通道里。谁关心这个业务事实,谁就去订阅对应通道,而不是系统按模块去“认领”事件。
这么做的好处是,当企业上第二套MES或做跨工厂平台集成的时候,比较占优。外部系统不需要理解你内部哪儿是质量模块哪是设备模块,只要知道这个业务域有哪些事实类型,就能订阅到需要的实时数据流。
