
在做工厂可视化电子看板这类项目时最头疼的往往不是单块大屏做得多漂亮而是多块大屏之间的数据能不能保持同步。我指的是那种实际交付现场产线1旁边挂一块屏产线2末端挂一块屏三楼会议室还有一块总览屏三块屏同时刷新同一批产量、设备状态、工单进度结果出现了中控室显示设备运行中、现场屏显示已停机这种鬼故事。这篇文章就来聊聊我是怎么定位这类问题的以及后续踩过哪些坑、怎么补上的调试方法。如果你现在正被多块大屏数据对不上折磨这篇内容应该能帮你少走不少弯路。1. 先别急着改代码分清三种典型的不同步症状接到现场反馈数据不同步时我第一件事不是打开代码看逻辑而是跑到机房和大屏旁边问清楚到底是怎么个不同步法。很多排查一开始就带跑偏了就是因为连问题描述本身都是模糊的——反正就是不一样这种话没有办法定位。实践下来工厂可视化电子看板的数据不同步大概可以分成下面三种症状。1.1 延迟型不同步数据总会到但总比别的屏慢几秒甚至几分钟这类现象是最常见的。会议室总览屏上的产量数字跟产线屏比总是慢半拍产线这边已经跳了七八个数会议室才缓缓跟上。最典型的反馈是这屏是不是设了5分钟刷新一次啊。延迟型问题的根子通常不在数据源而在传输链路或者前端取数方式上。比如会议室大屏用的是HTTP轮询3秒请求一次产线屏用的是WebSocket推送500毫秒就能收到一个消息。两种机制天然存在节奏差前端画面上自然就显得一快一慢。又比如后端推送服务做了批量聚合攒够50条消息才推一次那大屏看到的就不是实时数据而是带缓冲的数据。排查延迟型问题的时候重点要抓住延迟发生在哪一段。有两个小技巧很管用一是在画面上加一行最后更新时间后面跟一个毫秒级时间戳现场拍个照就能大致判断延迟量级二是用两个屏幕同时点开后端推送日志对比同一批次数据到达两边的时间差。如果A屏收到了、B屏没收到那就是B屏这条链路有问题如果两边都收到了但画面显示不一致那就是前端渲染层的问题。1.2 漂移型不同步两屏数据逐渐拉开差距最终完全对不上漂移型比延迟型更隐蔽。刚开机时几块屏数据一致跑了一个多小时后A屏显示产量1000件B屏显示950件C屏显示997件。这种逐渐拉开的过程几乎可以肯定是数据源本身不一致而不是传输快慢的问题。典型的漂移原因有三类。第一类几块屏查了不同的数据库表比如A屏查的是实时采集表、B屏查的是按小时汇总表两个表的计算口径不同。第二类多个后端实例分别连接了不同的缓存节点缓存未命中的时候回源不同步导致各实例拿到的基准值不一样。第三类前端本地做了增量计算有的屏通过浏览器端累加得到总数一旦中途断开推送增量就丢了或者重复加了最终数据和源端对不上。排查漂移型问题时我建议直接把几块屏的数据导出到Excel里做逐行对比找到从哪一条记录开始出现分叉。分叉点往往就对应着某个缓存失效时间点、某个服务重启时间点或者某条消息被重复消费的时间点比对时间一碰根因基本就浮出来了。1.3 闪烁/跳变型不同步同一个值在两块屏上反复横跳还有一种很令人崩溃的现象A屏显示设备运行中B屏显示设备停机然后过三秒两屏对调过来再过三秒又换回去看起来就像数据在打架。这种症状通常不是网络问题而是数据在采集端就出现了冲突——设备状态信号被两个采集程序同时读取一个读到了旧的寄存器值一个读到了新的寄存器值或者PLC里同一个状态位被两个点位映射成了不同含义。另外一个常见原因是消息乱序。后端推送了消息1和消息2WebSocket传输过程中消息2先到了前端消息1后到前端拿后到的消息1去覆盖状态画面就会跳回旧值。排查跳变型问题时打开后端的推送日志看seq序列号再看前端接收到的顺序基本十个乱序八个都是这么来的。2. 一条产量数据从产线到屏幕链路拆解才知道哪里会走样搞清楚症状之后还需要对数据从产生到上屏的完整链路有一个清晰的认知。工厂可视化电子看板的典型链路是PLC/数据库/传感器 → 采集服务 → 消息中间件或推送服务 → 大屏前端。每一层都有可能出现看来没问题但实际已经走样的细节我逐个说。2.1 采集层的二义性同一个设备为什么两边读到的值不一样现场设备的数据采集是分层分接口的。有的数据走OPC UA协议去PLC里读寄存器有的是直接查MES的视图还有的是通过Modbus网关转发。问题往往出在采集点位配置上同一个设备状态A点位映射的是寄存器的第3位B点位映射的是第4位PLC程序一改两个采集程序拿到的东西就各自为政了。更隐蔽的是采集频率不一致。一个采集服务是1秒轮询一次PLC另一个是10秒轮询一次。设备恰好在这10秒内发生了一次启停切换10秒级的服务可能就错过了整个状态变化两边数据自然对不上。所以我一直坚持一个原则在采集层统一做一次口径归一化。所有采集到的原始数据进入系统后先转换成一个统一的内部数据模型带上设备ID、指标编码、采集时间、原始值再分发出去。这样即使后端有多个数据源前端展示的也是统一口径后的数据不会出现一个指标多种含义。2.2 传输通道里常见的消息丢失和乱序采集服务拿到数据之后通过MQTT、Kafka、Redis Pub/Sub或者普通的TCP长连接推给前端大屏。这里头有一个容易被忽略的坑发布订阅模型天然不保证顺序和不重不漏。比如用了Redis的Pub/Sub当某个大屏客户端断线重连后Redis并不会补偿重放它离线期间的消息如果前端没有主动拉取一次最新快照那这个屏就永久性落后了。再比如Kafka同一个partition能保证分区内顺序但如果前端消费端做了多线程处理、或者对这个topic做了均衡策略消息的写入顺序和消费顺序就不一定一致了。前面说的跳变型不同步八成就是这种乱序导致的。传输层的另一个坑是连接被静默掐断。很多工厂现场的交换机、防火墙默认会对空闲的TCP连接做超时回收WebSocket如果长时间没有消息就挂在那边看着是已连接实际上后端已经写不进去了。等到前端下一次往服务端发消息时才发现断裂中间这段时间的数据就全丢了。这一点我在第4部分会重点讲因为它在交付现场引发的灵异事件特别多。2.3 前端渲染层的取数方式轮询、推送和定时器各有各的问题到了前端不同大屏的取数方式也会造成认知偏差。有的模块是setInterval定时器每隔5秒去调一次HTTP接口有的模块是WebSocket实时推送还有的模块是在收到推送后本机做一次累加计算。这三种方式混在一起画面上几个数字的新鲜程度完全不是一个量级。更麻烦的是浏览器对后台标签页的节流机制。如果一块大屏所在的浏览器标签页被切到后台或者被最小化浏览器会大量降低定时器的执行频率。Chrome能把后台标签页的setInterval压到每秒一次甚至一分钟一次这直接导致这块屏表面上在线实际上数据已经严重滞后。前端这块我的建议也明确凡是需要实时同步的大屏一律用WebSocket推送而且前端只做渲染不在本地做计算。所有总数、平均值、环比这类加工逻辑统一放后端算好再推给前端。前端本地如果需要临时维护一些状态务必以服务端下发的seq或时间戳为准不要自己拍脑袋累加。3. 调试方法我常用的四步定位法从对时间戳开始面对多屏不同步问题我有一套固定打法按步骤走基本能把问题框定到具体环节。这套方法不依赖什么高端工具只要后端日志、浏览器控制台和数据库查询权限就够了。3.1 第一步给每一条关键数据打上出生时间和序列号不同步排查最难的地方在于你不知道哪块屏的数据是基准、哪块是异常。所以我做的第一件事就是把数据的时间属性补齐。在数据库层面给关键业务表增加created_at字段精确到毫秒在推送接口的返回体里带上server_time和seq在前端页面上做一个隐藏的调试开关把接收到的最后一条消息的seq和server_time渲染出来。这样现场就很容易做对比了A屏显示总产值1256件数据时间14:32:18.123B屏显示总产值1260件数据时间14:32:18.129。两个屏的数据时间只差6毫秒但数值差了4件说明问题大概率不在传输延迟而在数据源或计算口径。反过来如果A屏数据时间已经到14:32:20了B屏还停在14:32:05那就是B屏这条链路明显滞后。3.2 第二步双端日志对齐看消息到底卡在哪一段加了时间戳之后就可以做链路追踪了。我习惯在后端推送服务里打印一行结构化的日志包括推送时间、目标屏ID、seq、指标名和值前端代码里也在收到消息时console.log一条同样的信息。现场排查时打开两块屏的浏览器F12控制台跟后端的推送日志一起对比。如果后端已经推了A屏也收到了B屏没收到那就是B屏的WebSocket连接有问题或者它的网络链路被中间设备挡了如果B屏收到了消息但画面没更新那就是前端渲染逻辑或者状态管理的bug如果后端根本没推那就是采集或聚合服务的问题。这段时间戳对比法还有一个好处可以快速判断后端推了但前端没有渲染是事件机制的问题还是线程调度的问题避免在前端代码里漫无目的地瞎排查。3.3 第三步观察连接数WebSocket断链是最容易忽视的事故源很多大屏数据不更新乍看是数据同步问题实际是客户端根本没连着服务器。我常用的命令是netstat -an | grep 端口号看服务器上对应端口的连接状态重点看ESTABLISHED数量再对比前端应该在线的大屏数量。比如现场有4块屏在线但服务器上这个端口只有2个ESTABLISHED连接那不用想了另外两块屏已经悄悄断线了。WebSocket断链之后如果前端没有重连机制大屏就会永远停留在最后收到的那个画面。而且断链往往不是立即发生的而是经过很长一段时间之后才被发现——因为大屏这种设备就挂在显眼位置白天没人盯着看晚上值班人员扫一眼才发现画面冻住了。这一步顺便也要检查Nginx或其他反向代理的配置。如果你用Nginx代理了WebSocket默认proxy_read_timeout是60秒一旦超过60秒没有数据从后端返回连接就会被Nginx掐断。对看板这种大部分时间消息很稀疏的场景来说这是一个几乎必然踩中的坑。解决办法是显式设置足够长的超时时间。3.4 第四步清空缓存做对比排除页面自己攒数据出错排查到前端这一层时我还有一个习惯把所有大屏的浏览器缓存、localStorage、IndexedDB全部清空同一时间重新刷新。这个操作看起来粗鲁但非常有效。因为有些前端版本在本地存了历史数据用于展示趋势图一旦本地缓存损坏或者被写脏画面上就会混入陈年数据。清空缓存后如果所有大屏都能在一分钟内恢复正常且数值一致那说明问题出在本地状态管理的持久化逻辑上——这是相当多见的情况。还有一个极端情况我也碰到过机房的NTP服务坏了服务器时间比实际时间慢了20分钟。前端页面上的距上次刷新3秒前、最后更新时间14:12这些展示全靠本地时间兜底结果几块屏因为浏览器所在电脑的系统时间快慢不同显示出完全不同的相对时间。最开始我还以为是网络问题折腾了半天才发现是时间基准坏了。所以做对时校验时不妨在服务器上执行一下date命令再看每台终端的时间先把时间基准统一了再谈同步。4. 交付现场最容易踩的四个隐蔽地雷这一部分的内容都是真金白银换来的经验。很多时候数据和代码逻辑都是对的但掉进下面这些环境里照样会出各种不同步问题。4.1 Nginx代理把WebSocket连接掐了默认60秒必掉线工厂大屏的绝大多数WebSocket都需要经过Nginx做反代但你如果不做额外配置默认的proxy_read_timeout 75s就会让空闲连接被回收。大屏场景下消息是有事件才推可能几十秒甚至几分钟都没有一条消息这期间连接就处于空闲状态一到时间就被Nginx切断。典型的修复配置长这样location /ws { proxy_pass http://backend_websocket; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_read_timeout 3600s; proxy_send_timeout 3600s; }proxy_read_timeout是从上游读取数据超时proxy_send_timeout是发送数据超时两个都调大连接就不会被轻易回收。这是我在踩过一次每天下午3点左右屏必然集体掉线的坑之后记住的——为什么是下午3点因为车间中午断电休息下午重新开机后大屏连接建立空闲一小时后集体被断。4.2 浏览器后台标签页节流大屏被最小化就会停更前面提到过浏览器的后台标签页节流这里单独拎出来说是因为它太容易中招了。工厂的Windows大屏电脑上经常开着好几个页面大屏一个标签页占用整个显示器旁边还开着监控页面、Excel表格等。只要大屏所在标签页不是当前激活页Chrome就会把定时器节流到很低的频率WebSocket消息虽然还是会收到但前端定时器驱动的UI更新、动画过渡都会卡住。解决办法有两个方向。第一个是改架构尽量用WebSocket消息本身驱动UI更新不要依赖setInterval去重绘第二个是加页面可见性处理监听visibilitychange事件当页面重新可见时立刻重新拉取一次全量数据并刷新画面。document.addEventListener(visibilitychange, () { if (document.visibilityState visible) { refreshBySnapshot(); // 主动拉取最新快照 } });这个方法既能解决后台节流导致的停更也能解决WebSocket断线期间数据漏掉的问题属于成本低效果显著的一种兜底。4.3 服务器钟表时间乱套所有时间戳对比全部失效排查数据同步问题几乎每一步都依赖时间对比。但你有没有想过服务器时间和现场终端的时间可能根本不在一个星球我有一次遇到的情况是后端推送日志显示14:30推送了某个值前端收到的消息时间戳却是14:29两屏对比时始终对不齐怎么查都查不出来。最后我在后端服务器上执行了一下date发现服务器时间已经慢跑了将近一个小时而数据库里的created_at用的是数据库服务器自己的时间推送消息用的是应用服务器的时间两个服务器又差了十几分钟。时间基准四分五裂写什么日志都白搭。所以项目启动时必须做的一件事是统一内网所有服务器、数据库、终端的NTP时间同步。写进交付文档循环检查。用系统自带的时间同步服务也好用一个固定的内网时间服务器也行必须保证所有参与数据链路的设备都在用同一个时间源。4.4 Redis缓存策略引发的缓存漂移很多可视化看板项目会用到Redis做热数据的缓存比如设备状态、当前产量。这里有一个容易踩的坑如果大屏请求量大后端会做缓存读取但如果缓存的数据更新策略不当比如缓存过期时间是5分钟而有些屏的数据被缓存在了旧key上有些屏走了新key就会出现各屏数据不一致。更隐蔽的是Redis集群或多实例场景。如果后端服务是多个实例部署每个实例连的是不同的Redis节点那某个实例更新的数据没有及时同步到其他节点各实例对前端返回的结果就可能不同。这种情况通过连接信息排查容易忽略因为所有人第一反应都是Redis不是单机的吗。我建议在调试时调用info replication或cluster info看看Redis本身的角色和数据状态。如果发现是从节点落后了就要检查主从同步配置或者网络延迟。一句话总结别把用了Redis当成数据一定一致缓存穿透、缓存同步、过期策略都要纳入调试范围。5. 根治方案在架构层面把不同步按死调试方法解决的是当前故障但要在大屏项目里少隔三差五出问题必须从架构上做几个预防性设计。这些措施和前面排查的经验是一体的既有防御作用也让将来的排查更容易。5.1 WebSocket推送为主、轮询兜底的双通道策略大屏这类场景纯轮询体验差纯WebSocket又怕断链和边缘情况。稳妥的做法是双通道结合正常状态下走WebSocket实时推送同时前端每隔30秒做一次轻量级的快照拉取用于校正偏差。可能有人会觉得30秒全量拉一次太浪费但这个快照接口可以做成轻量版——只返回每个指标的最新值、数据时间和总seq不返回历史明细。这样即使WebSocket偶尔丢一条消息快照也能在30秒内把这个洞补上。两个通道的数据在页面上显示时以seq大的一方为准。这套策略跑下来实际效果是平时的实时性由WebSocket保证兜底纠错由快照保证两块并行之后屏幕数据对不上的概率可以降到非常低。如果资源紧张快照间隔可以放宽到60秒但千万别去掉这个兜底。5.2 全局递增序列号统一时间源让所有屏讲同一种时间语言所谓全链路不同步本质上是各个节点对当前状态的认知不同步。如果只靠时间戳服务器之间时钟有偏差时就不可靠了。所以我习惯在数据协议里引入全局递增序列号——每次后端生成状态快照或推送消息时都带一个单调递增的seq前端收到消息后只认seq更大的数据才渲染seq小的可以判定为过期消息直接丢弃或排队。这套机制的好处是即使消息乱序到达前端也能根据seq判断出真正的顺序避免旧消息覆盖新消息。配合第3.1步中讲的时间戳显示排查时一眼就能看出某个屏到底落后了多少条消息。同时前文提到的NTP统一时间源也必不可少。时间戳和seq双轨并行一个看真实发生的时刻一个看系统内的先后顺序互为参照对付工厂现场常见的杂七杂八环境足够用了。5.3 屏端自愈机制检测到落后就主动自救最后一层保障是让大屏自己察觉到异常并恢复。我实现了一个简单的落后检测逻辑前端维护一个期望收到的最新seq如果连续若干秒没有收到新的seq或者收到的seq比期望值小很多就触发一次全量快照拉取并把之前的本地状态重置。结合流程图来看接收消息 → 判断seq是否连续 → 连续就直接渲染有跳跃或停滞 → 调用快照接口全量校正 → 校正成功后再等待下一批WebSocket消息。这个自愈逻辑不用写得很复杂重点是能用自动化代替人工去开机刷新。工厂大屏分布在车间各个角落一旦数据不同步靠人去现场重启是一种极大的浪费。让屏幕自己发现问题、自己纠正是最终极的解决办法。6. 最后的经验小结做了这么多个工厂可视化电子看板项目我自己的体会是大屏数据同步问题的排查80%的时间都在做数据链路的对齐——对齐时间、对齐seq、对齐日志、对齐缓存。只要数据从采集到渲染的每一段都有明确的标记所谓不同步就只是找到分叉点那么简单的操作。反过来如果链路里的数据连时间戳都没有那只能靠肉眼和运气去猜效率极低。给正准备接类似项目的朋友一个建议在项目开发阶段就先把时间戳、日志、seq这些基础设施做进去不要等到交付验收被现场发现问题了再回头补。这些东西在演示的时候看起来可有可无但到了车间现场它们就是你在一条昏暗的产线上快速定位故障的唯一依靠。多看屏、多对时、多造异常场景自测不同步这个老问题就没有想象中那么可怕。