ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

工控网关断网不丢数据:基于Transactional Outbox模式的可靠数据采集方案

工控网关断网不丢数据:基于Transactional Outbox模式的可靠数据采集方案 1. 项目概述工控网关的断网之痛与 Outbox 的解药干工控这行的朋友十有八九都经历过这样的场景车间里的 PLC可编程逻辑控制器正往网关吐出生产数据网络突然闪断几秒钟后又恢复。看起来好像没事但等到数据中心那边对账的时候发现中间一段数据就这么凭空消失了。产量少了几个件、温度曲线缺了一段、设备报警记录对不上号这就是典型的断网丢数据事故。更麻烦的是很多工控协议本身就不带重传机制或者重传逻辑写得极其简陋一旦网络抖动数据就真的再也找不回来了。我最早碰到这个问题是在一个产线数据采集项目上。当时用的工控网关负责把 Modbus TCP 协议的数据转换成 MQTT 推到云端现场网络环境比较恶劣交换机偶尔会重启光纤收发器也会因为灰尘导致丢包。最开始我天真地以为MQTT 的 QoS 1 能帮我兜底结果实测下来断网超过一定时间之后数据照样丢。后来反复排查才发现消息已经从网关进程发出去了但网络根本没通MQTT broker 那边压根没收到而本地的 QoS 机制又因为连接已经断开而失效。真正解决这个问题的思路是后来在研究分布式系统的一致性方案时想到的。业界有一个非常成熟的模式叫 Transactional Outbox原本是解决微服务架构下数据一致性的问题核心思路是“先写本地数据库再异步发布消息”。我把它平移到了工控网关的场景里结果效果出乎意料地好。这篇文章就把我的完整实现方案、踩坑经历和优化过程都写出来希望能给正在被类似问题折磨的同行一点参考。这套方案适合谁来参考如果你在做工业数据采集、边缘计算网关、SCADA 系统对接或者任何需要在不可靠网络上保证数据不丢的场景这篇文章都能帮到你。哪怕你不是搞工控的只要接触过物联网网关、车联网终端这类场景Outbox 模式的思路也完全通用。2. 核心原理为什么 Outbox 模式能避免断网丢数据2.1 传统方案的漏洞断网瞬间的竞态条件在讲 Outbox 模式之前我们先得搞清楚传统方案到底哪里容易出问题。最常见的做法是网关进程收到 PLC 数据后直接在内存里做业务处理然后调用 MQTT 客户端的方法往 broker 发送消息。这个流程看起来没问题但里面藏着一个致命的竞态条件。假设网关在时间点 T 发送消息网络在这个瞬间断开。MQTT 客户端库的表现通常是这样的TCP 连接还没被系统检测到断开publish函数正常返回成功。但消息实际上只进了系统 socket 缓冲区根本没离开网卡。等过几秒 TCP 超时客户端才意识到连接断了这时候消息要么被丢掉要么触发重连后的重发逻辑——但很多 MQTT 客户端库的 QoS 1 重发机制只针对 session 恢复的情况普通场景下消息早就没了。还有更隐蔽的情况。有的网关头尾相接处理数据一条数据进来了处理完成就丢了然后才去发送。如果发送失败这条数据就彻底无处可寻了。即使有内存队列网关一重启队列里的数据也全部清零。这就是很多项目里“断网必丢数、重启必丢数”的根本原因。2.2 Transactional Outbox 的核心思想先落库后发送Outbox 模式的思路非常简单粗暴与其在内存里折腾不如把要发送的数据先写到本地磁盘上的一个表里这个表就叫 Outbox 表。写入和业务处理放在同一个事务里保证业务数据和待发送数据同时成功、同时失败。然后由一个独立的发送进程去扫这张表不断把里面未发送的数据推送到远端发送成功的就标记为已发送或直接删除。这个模式原本是为了解决微服务下订单系统和积分系统之间的一致性问题——订单都记在本地事务里了不可能出现“订单创建成功但积分没加”这种分裂情况。放到工控网关场景里道理完全一样PLC 数据进来到网关这一步是确定的、本地的那我把数据先存起来就是最稳妥的保障。网络通了就发出去网络断了数据还在磁盘上躺着网络恢复之后接着发。数据是否发送成功由远端确认来决定而不是由本地网络状态来判断。从本质上看Outbox 模式是把“网络是否可达”这个不确定因素从数据链路的必经之路上剥离出去了。数据先进入一个可靠的本地存储中发送则变成了一个异步的、可重试的过程。这就好比寄快递你把包裹放进快递柜快递柜会给你一个回执哪怕快递员晚来两小时包裹也不会丢只是会晚点到达。2.3 与本地缓存方案的对比为什么 SQLite 比内存队列强也有人问我用内存队列不也能实现缓冲吗为什么非要落盘我做过对比测试结果非常明显内存队列在网关进程正常运行时能起到缓冲作用但一旦进程崩溃、断电、重启内存里的数据全部灰飞烟灭。而工控网关最怕的就是死机重启——很多时候网络断开和设备重启是连在一起的比如交换机供电出问题导致了连锁反应这时候内存方案就直接失效了。那用文件行不行比如写 JSON 文件或者 CSV 文件。可以但文件方案的坑在于并发写入和部分写。如果数据量一大多个线程同时写一个文件日志会交错如果写一半断电文件损坏数据照样丢。相比之下SQLite 作为嵌入式数据库ACID 事务是它的看家本领支持原子提交即使中途断电也不会损坏数据文件。而且 SQLite 查询灵活我可以很方便地按状态、按时间检索数据做重发和过期清理都很顺手。从我个人经验来看SQLite 是工控网关上落地 Outbox 模式的最佳载体。它在嵌入式环境里的性能足够用——单条写入次数每秒上千次没问题而这远超一般 PLC 数据的采集频率。加上 SQLite 的 WAL 模式Write-Ahead Logging预写日志可以显著提高并发读写能力还能避免读操作阻塞写操作这让它非常适合“频繁写入、异步发送”的 Outbox 场景。当然对于性能要求极其苛刻的场景可以考虑用 LevelDB 这类嵌入式 KV 存储但 SQLite 的成熟度和稳定性是它最大的优势。3. 实操拆解在工控网关里落地 Outbox 模式3.1 架构总览数据流转的每一个环节我先画一下我最终实现的整体架构这样你在自己动手的时候心里有个底采集层通过 Modbus TCP、OPC UA、S7 协议等从 PLC 读取数据写入层把原始数据和处理结果同时写入本地 SQLite 的业务表和 Outbox 表发送层一个独立的发送线程/进程轮询 Outbox 表中“待发送”状态的数据运维层记录发送日志、处理失败的记录、定期清理过期数据这套架构的关键点在于采集层只是把数据写到本地不关心远端是否可达发送层只关注 Outbox 表里有没有待发送的数据不关心数据是怎么来的。两层各司其职解耦之后断网丢数据的问题就从原理上被消灭了。这条链路里任何一个环节出现故障数据都停留在本地的安全区里不会凭空消失。3.2 表结构设计Outbox 表的关键字段表结构设计是整个方案的地基。我调试过好几版最终固定下来的表结构是这样的CREATE TABLE IF NOT EXISTS outbox ( id INTEGER PRIMARY KEY AUTOINCREMENT, topic TEXT NOT NULL, -- MQTT主题或目标标识 payload TEXT NOT NULL, -- 数据内容JSON格式 qos INTEGER DEFAULT 0, -- MQTT QoS级别 status INTEGER DEFAULT 0, -- 0待发送, 1已发送, 2发送失败 retry_count INTEGER DEFAULT 0, -- 重试次数 create_time DATETIME DEFAULT CURRENT_TIMESTAMP, -- 创建时间 last_attempt_time DATETIME, -- 最后尝试发送时间 next_attempt_time DATETIME, -- 下次尝试时间用于退避 remark TEXT -- 备注信息方便排查 ); CREATE INDEX idx_outbox_status_time ON outbox(status, next_attempt_time);这个表设计的几个关键点我得说一下status字段单独拎出来是为了发送线程能用一条简单的 SQL 快速定位所有待发送数据SELECT * FROM outbox WHERE status 0 AND next_attempt_time CURRENT_TIMESTAMP。retry_count用于记录重试次数达到上限就直接标记为最终失败避免无限重试导致队列堆积。next_attempt_time是实现指数退避的关键发送失败后不能立刻疯狂重试而是要让失败的数据“冷静”一下。索引的设计也讲究。刚开始我只对status建了索引结果数据量大之后查询变慢。后来我改成联合索引(status, next_attempt_time)发送线程的扫描效率提升了一个数量级。工控网关本身性能不强索引设计得好不好直接决定了高数据量下的表现。3.3 写入逻辑业务表与 Outbox 表的事务一致性数据写入这一步是整个方案的灵魂所在。我强调过很多次一定要把业务数据的写入和 Outbox 表的数据写入放在同一个 SQLite 事务里。我来演示一下伪代码import sqlite3 import json import time def save_data(device_id, value): conn sqlite3.connect(gateway.db) try: conn.execute(BEGIN) # 业务表写入 conn.execute(INSERT INTO device_data(device_id, value, ts) VALUES(?, ?, ?), (device_id, value, time.time())) # Outbox表写入 payload json.dumps({device_id: device_id, value: value, ts: time.time()}) conn.execute(INSERT INTO outbox(topic, payload, qos) VALUES(?, ?, ?), (fdevices/{device_id}/data, payload, 1)) conn.commit() except Exception: conn.rollback() raise finally: conn.close()这样做的效果是如果业务数据成功写入那么 outbox 表里必然有对应的记录。如果业务数据写入失败那么 outbox 里也不会有脏数据。永远不会有“数据处理了但没发出去”的情况也不会有“数据没处理但发出去了”的情况。这里有个容易犯的错误很多人图省事先写业务表提交事务再单独写 outbox 表。结果两个操作之间如果发生崩溃就会产生“业务表有数据、outbox 表没数据”的缺口这正是我们要避免的。记住两条 INSERT 必须在同一个事务里缺一不可。另外关于事务的性能问题我刚开始担心频繁开事务会导致 SQLite 变慢。实测下来在 WAL 模式下开启事务批量写入 100 条数据耗时只有几十毫秒级别完全够用。不建议单独一条条开事务提交因为每个事务都有 fsync 开销批量处理能显著提升吞吐量。你可以根据实际采集频率决定是每条一个事务还是攒批提交但务必保证同一批的业务表和 outbox 表在同一个事务里。3.4 发送层实现可靠投递的核心逻辑发送层是保障可靠投递的引擎。它做的事其实很机械不断扫表、发送、更新状态。核心逻辑如下def send_loop(): while True: rows fetch_pending_records(limit100) for row in rows: try: mqtt_client.publish(row[topic], row[payload], qosrow[qos]) mark_sent(row[id]) except Exception as e: handle_failure(row, e) time.sleep(0.1) # 控制轮询频率这里面有几个关键细节要处理发送确认MQTT 客户端库的publish方法只是把消息交给了底层库不代表 broker 真的收到了。要真正确认是否投递成功得通过回调机制。以 paho-mqtt 为例publish之后可以等on_publish回调触发或者用wait_for_publish()阻塞等待确认。只有收到确认的回调才应该把status改为已发送。超时机制网络故障时publish可能会一直阻塞在底层。必须给发送操作加超时超时了就按失败处理不能让发送线程卡死在一个数据上。超时时间我一般设 5 到 10 秒具体看网络环境。状态转换发送失败后在重试时不要立刻把状态改为失败。我的做法是发送失败后retry_count加一status保持 0但next_attempt_time设置为当前时间加上退避延迟。这样发送线程在下一次扫描时会自动跳过还没到时间的记录实现平滑的退避重试。发送层的代码不难写难的是处理好各种异常分支。要把“网络断开”“broker 不可达”“超时无响应”“认证失败”这些情况都考虑到并在异常处理中统一走失败重试的路。3.5 消息去重与幂等性避免网络恢复后的重复投递Outbox 模式解决了丢数据的问题但引入了一个新的问题重复投递。假设数据已经发送成功broker 也返回了确认但在我们更新status之前网关重启了。恢复后扫描这条数据还是“待发送”状态于是又发送了一遍。这种情况在网络环境差、确认包丢失时非常常见。解决重复投递有两条路一条是在网关上做精确的一次性语义Exactly-Once这在分布式系统里代价很高另一条是在下游消费端做幂等处理让重复数据不造成负面影响。工控场景里我推荐走第二条路因为下游通常是数据库或者消息队列做幂等相对容易。具体做法在 payload 里加入msg_id字段用 UUID 或者自增 ID 保证唯一。下游消费端收到消息后先查一下这个msg_id是不是已经处理过了是的话就丢弃。用 SQLite 的话可以在消费端的接收表里为msg_id建唯一索引CREATE TABLE IF NOT EXISTS received_messages ( msg_id TEXT PRIMARY KEY, received_at DATETIME DEFAULT CURRENT_TIMESTAMP ); INSERT OR IGNORE INTO received_messages(msg_id) VALUES(?); -- 如果影响行数为0说明重复消息直接丢弃这种做法能确保即使网络抖动导致网关重复发送下游收到并处理的数据也不会重复计数。实测中加入幂等处理后数据重复率从百分之几降到了零。这是在工程上非常实用的取舍用最小的成本解决重复问题而不是费尽心力去追求发送端的完全精确。4. 进阶优化让 Outbox 模式真正扛住生产压力4.1 断网恢复后的积压处理策略断网时间越长Outbox 表里积压的数据就越多。网络恢复的那一刻所有数据会同时涌向发送线程如果发送线程的处理能力跟不上反而会出现新的瓶颈。这里我总结了几种应对策略第一个策略是限速发送。不要一窝蜂把所有积压数据同时发出而是要控制发送速率。比如每秒最多发 200 条超过这个数就让发送线程 sleep。这样做的好处是避免瞬间流量冲击下游系统也避免 MQTT broker 因为大量并发连接而拒绝服务。我在实际项目中就吃过亏——断网一小时恢复后网关把积压的几万条数据一口气往 broker 灌直接把 broker 干崩了。后来加了限速问题才彻底解决。第二个策略是有序发送。有些工控数据是有时序要求的比如设备状态的变化过程先发生的事件不能后发。我在 outbox 表里用自增 id 作为主键天然能保证插入顺序。发送线程按 id 升序发送能最大程度保证数据到达顺序与产生顺序一致。但要注意如果下游对乱序容忍度很低建议在 payload 里也带上设备时间戳下游按时间戳排序处理。第三个策略是过期数据清理。有些数据比如实时温度值时效性很强断网一小时之后补发可能已经失去意义。这种情况下可以给 Outbox 表加一个过期策略超过一定时间比如 10 分钟的待发送数据直接标记为丢弃或者发送时带上“时间戳距今已超过阈值”的标记让下游决定是否丢弃。这个策略要按业务场景灵活配置不能一刀切。4.2 存储管理与 WAL 模式调优Outbox 表的数据会不断累积如果不做清理SQLite 文件会越来越大。我见过有的网关跑了一年数据库文件好几个 GB查询越来越慢。定期清理势在必行。我的做法是每次扫描发送完数据后顺手删除已发送且创建时间超过某阈值的历史记录例如保留最近 7 天的数据。用一条 DELETE 语句就能搞定DELETE FROM outbox WHERE status 1 AND create_time datetime(now, -7 days);如果你担心删除操作会影响数据完整性可以改成标记归档但工控场景里一般没有这个必要数据已经发送到远端了本地记录更多是日志性质的备份。SQLite 的 WAL 模式对 Outbox 场景很有帮助。开启方法很简单conn.execute(PRAGMA journal_modeWAL)WAL 模式最大的好处是读操作不会阻塞写操作这对“写得很频繁、读也在持续进行”的 Outbox 场景很匹配。发送线程频频查表同时采集线程在不断写入如果没有 WALSQLite 的锁机制会导致查询等待写入完成产生额外的延迟。另外WAL 模式下数据库文件分为主文件和-wal文件记得在备份和清理脚本里把这两个文件都处理到。还有一个容易被忽略的点SQLite 的连接时效。默认情况下SQLite 连接超时时间可能只有几秒。在长时间运行的生产环境里如果某个操作持有了数据库锁其他连接很容易报database is locked。我强烈建议在初始化连接时设置timeout30甚至更长同时通过busy_timeout统一管理锁等待行为。这块我在早期踩过不少坑后面排查到夜深人静时才发现是锁等待超时导致的偶发写入失败。4.3 多线程与多实例的并发安全工控网关上经常有多个采集任务并行运行如果多个线程同时往 SQLite 写数据并发控制就变得很重要。SQLite 本身对多线程支持有限简单的做法是让所有数据库操作走同一个连接并在外部加锁。更稳妥的做法是用连接池每个线程一个独立连接但用busy_timeout来处理竞争。如果网关有多个 CPU 核心你甚至可以考虑用多个发送线程来并发处理 Outbox 表。但要注意多个发送线程同时处理同一条数据可能导致重复发送。解决办法是给扫描加条件让每个线程处理不同的数据段比如按id % num_threads进行分片。不过按照我的经验工控网关的数据量一般到不了需要多发送线程的程度一个线程加合理轮询已经足够。除非你处理的是极高频的采集场景比如每秒钟几千个点位否则不必过早优化并发。把基本功能做可靠远比追求极致的并发性能重要。工控场景里稳定压倒一切。4.4 异常处理与看门狗机制最后一个进阶话题是网关自身的健壮性。Outbox 模式依赖 SQLite 和发送线程的正常运行如果 SQLite 文件损坏或者发送线程因为某个 bug 挂掉数据同样发不出去。建议给发送线程加一个看门狗机制主进程定时检查发送线程的心跳如果发送线程长时间没有更新心跳时间戳就把它重启。SQLite 文件损坏的问题也要提前预防。在每次启动时运行PRAGMA integrity_check可以快速检测数据库文件的完整性。如果检测到损坏可以尝试PRAGMA recover恢复或者直接使用之前的备份文件。另外定期备份 SQLite 文件是必要的——即使数据已经发送到远端本地备份依然能在对账和排查问题时发挥关键作用。5. 常见问题与排查技巧实录5.1 典型故障现象与解决方案速查表我在多个项目里落地 Outbox 模式后遇到的高频问题基本可以整理成下面这张表。你能遇到的问题大概率都跑不出这个范围:故障现象可能原因排查思路和解决方案数据发送成功但下游查不到下游消费逻辑没做幂等重复数据被当成新数据处理或者消息路由配置有误检查下游日志确认msg_id是否在消费端被正确处理核对 topic 映射关系断网恢复后 broker 被压垮积压数据瞬间全部发出没有限速在发送循环里加限速逻辑控制每秒最大发送条数SQLite 报database is locked连接超时时间设置过短多个连接竞争锁初始化连接时设置timeout并开启 WAL 模式数据重复发送发送成功但status更新前进程重启属于至少一次语义的自然结果下游加幂等处理用msg_id唯一约束去重Outbox 表无限增长没有定期清理已发送数据或者清理任务被发送线程阻塞在扫描循环末尾加 DELETE 清理语句删除过期已发送记录网关重启后偶发数据丢失只在内存中维护待发送队列没有落盘检查是否所有业务写入都走SAVE DATA OUTBOX同一事务确认没有绕过 outbox 的直接发送路径5.2 一个典型的排查过程复盘我印象最深的一次故障发生在部署 Outbox 模式后的第三个星期。运维反馈说某化工厂的网关有大量数据延迟发送线程日志里全是超时。我远程登录上去第一件事是查 outbox 表的积压情况SELECT status, COUNT(*) FROM outbox GROUP BY status;结果显示“待发送”状态的数据超过 10 万条而且还在不断增长。再往下查SELECT * FROM outbox WHERE status 0 ORDER BY id DESC LIMIT 10;发现retry_count已经很高每条数据的next_attempt_time都设到了几小时以后。这说明发送线程一直在失败重试而且退避策略设置得不合理。我进一步排查网络发现该工厂的 MQTT broker 配置有误broker 地址在本地 DNS 服务宕机后无法解析。这是个典型的环境问题不是 Outbox 模式本身的问题——如果没有 Outbox 表这些数据早就丢光了。我修正了 broker 地址配置等网络恢复后积压的 10 万条数据在几分钟内全部发送完毕一条没少。这件事让我意识到Outbox 模式的价值不只是断网兜底更是给排查问题留下了窗口。传统方案里数据丢了就丢了连日志都没有有了 Outbox 表你能精确看到每一条数据的生命周期这对定位问题有极大的帮助。5.3 新手容易踩的三个隐性坑第一个坑是 MQTT 的clean_session设置。如果你用 QoS 1 发送消息但客户端连接时设置了clean_sessionTrue那么断线重连后broker 不会保留任何未完成的会话状态未确认的 QoS 1 消息就会丢失。我把这个参数改成False之后QoS 1 消息的可靠性才真正体现出来。不过要注意clean_sessionFalse会让 broker 维护会话状态需要及时清理过期会话否则 broker 内存会越占越多。第二个坑是 payload 里的特殊字符。PLC 数据里有时会包含二进制值或者特殊符号如果你直接拼字符串很可能在序列化或反序列化时出错。我强烈建议所有 payload 统一用 JSON 格式并且根据实际类型严格定义字段。曾经有个项目因为同事图省事直接在 payload 里塞了一个时间戳字符串结果下游解析时因时区问题导致时间全部偏移对账时才发现。第三个坑是时区不一致。网关本地时间、PLC 时间、MQTT broker 所在服务器时间可能来自三个时区。如果你不在数据里显式带上时区信息后续任何跨系统的数据对齐都会变成灾难。我的建议是统一使用 UTC 时间戳无论是采集时间还是入表时间都存 UTC展示时再转本地时间。6. 实战案例一个完整的断网模拟测试理论说得再多不如亲手验证一次。下面是我在一套模拟环境里做的完整断网测试记录了整个过程和结果。6.1 测试环境与场景设定测试环境是这样的一台工控机装 Ubuntu跑了三个程序——采集模拟器模拟每 100 毫秒生成一条温度数据、网关主程序集成了 Outbox 模式、Mosquitto MQTT broker。另外有一台订阅端负责接收数据并写入 MySQL 数据库。我的测试场景是正常运行 30 分钟然后物理断开网卡的网线 15 分钟期间采集模拟器持续运行产生数据再恢复网络观察数据能否完整到达订阅端。这个模拟基本能覆盖生产环境中最常见的断网情景。6.2 断网期间的网关行为观察断网期间我重点观察了几个指标outbox 表的数据量持续上涨从 0 涨到 9000 多条发送线程的日志里不断打印连接失败和重试信息retry_count在稳步增加next_attempt_time按照指数退避算法不断向后调整从最初的 1 秒逐渐拉长到 64 秒。这期间 CPU 占用率正常内存也没有明显增长SQLite 的 WAL 文件大小稳定没有出现膨胀。最让我放心的是网关的进程状态即使网络长期不可用发送线程也没有卡死或者崩溃主进程依然能正常响应新的采集数据。这意味着断网只影响了“发送”这个能力并没有影响“采集”和“存储”的核心功能。6.3 网络恢复后的数据核对结果网络恢复后大约 2 分钟outbox 表里的“待发送”数据就清零了。然后在订阅端的 MySQL 里跑了一个对账查询按照msg_id去重统计发现断网期间产生的 9835 条数据全部到达数据产生时间在断网区间内通过网络恢复后的重复计数检查没有重复数据入库幂等生效数据到达订阅端的时间顺序与产生顺序基本一致略有抖动但可接受数据从断网开始到最终补齐的最长延迟约 3 分 40 秒完全在业务容忍范围内在对比测试中我用同一个采集模拟器跑了一套没有 Outbox 的对照组。断网 15 分钟后恢复对照组只收到了断网前最后几秒的数据和恢复后的数据中间 15 分钟的数据全部丢失丢失率 100%。同样是断网差别就这么大。这次测试给了我很大信心。后来我把这套方案部署到几个真实的工厂现场经历了交换机固件升级、光纤被老鼠咬断、供电闪断等各种状况数据一次都没丢过。如果你想在自己的环境里验证我建议可以先跑一遍这样的对照测试心中就有底了。7. 最后再分享两个调试小技巧第一个技巧是开启 SQLite 的调试日志。如果你怀疑 outbox 表读写有问题可以在初始化数据库连接时执行conn.set_trace_callback(print)这样每条 SQL 语句都会打出来方便定位是不是有语句执行错误或慢查询。生产环境里这个功能要关掉不然日志量太大。第二个技巧是给发送线程加一个“空循环”保护。当 outbox 表里没有任何待发送数据时发送线程的循环不能空转否则 CPU 占用会很高。我的做法是扫描结果为空时time.sleep(1)休眠 1 秒有数据时处理完一批后time.sleep(0.1)。这样既能保持低延迟又不会无谓消耗 CPU。这个细节看着小但在低功耗的 ARM 工控板上性能影响很大。Outbox 模式在工控网关里的价值一句话概括就是把“实时发送”的强要求降级成了“可靠发送”的弱要求让断网从数据事故变成了一次普通的网络波动。数据和网络解耦之后哪怕网络烂到极致我也能拍着胸脯说数据不会丢。这样的底气对一个做工控的人来说比什么花哨的架构都重要。
返回列表