1. 工业传感器数据采集方案的整体设计与选型思路
1.1 为什么工业现场的数据采集不能照搬互联网那套
干了十几年工业自动化,我见过太多项目在数据采集这一环翻车。互联网那套 HTTP 轮询、RESTful 接口,放到车间里根本跑不通——PLC 的扫描周期是毫秒级,你一个 HTTP 请求发过去,光 TCP 三次握手加 JSON 序列化就几十毫秒没了,更别说车间里电磁干扰大、网络抖动频繁,丢一个包可能就意味着一条产线的关键参数丢失。
工业传感器数据采集的核心诉求跟互联网完全不同:确定性、实时性、可靠性。确定性指的是采集周期必须稳定,不能这次 100ms 下次 500ms;实时性要求从传感器到上位机(或云端)的延迟可控;可靠性则意味着断网、断电、设备重启后数据不能丢,至少不能丢关键数据。
所以这套方案的设计思路很明确:分层采集、协议归一、边缘缓冲、按需上云。底层用 Modbus RTU/TCP 这类轻量协议跟传感器和 PLC 打交道,中间层用 OPC UA 做语义化建模和跨厂商互通,上层用 MQTT 做发布订阅式的数据分发。这三层各司其职,谁也不越界。
1.2 三种核心协议的分工与选型逻辑
很多人一上来就问“Modbus、OPC UA、MQTT 到底选哪个”,这个问题本身就问错了。它们不是竞争关系,而是不同层级、不同场景的互补工具。
Modbus是工业现场的“普通话”。它简单、成熟、几乎所有的 PLC、仪表、变频器都支持。Modbus RTU 跑在 RS485 总线上,一主多从,接线简单,成本极低;Modbus TCP 跑在以太网上,速度更快,适合设备密集的车间。但 Modbus 的短板也很明显:没有数据类型定义,寄存器里放的是整数还是浮点数、单位是什么、量程多少,全靠文档约定,换个厂家就得重新对表。
OPC UA是工业 4.0 的“世界语”。它自带信息模型,每个变量都有明确的语义、数据类型、工程单位,还支持订阅模式和内置安全机制。西门子、倍福、罗克韦尔这些主流厂商的新一代控制器都原生支持 OPC UA。但 OPC UA 对硬件资源要求高,老设备跑不动,而且配置复杂度比 Modbus 高一个数量级。
MQTT是数据上云的“快递员”。它基于发布订阅模式,一个 Broker 可以连接成千上万个客户端,消息开销极小(最小报文只有 2 字节),支持 QoS 0/1/2 三级消息可靠性。特别适合把采集到的数据从边缘网关送到云端或数据中心。但 MQTT 本身不解决设备接入问题,它只管传输,不管数据从哪来、是什么含义。
我的选型原则是:现场设备能走 Modbus 就走 Modbus,新设备优先 OPC UA,上云统一走 MQTT。如果现场有西门子 840D sl 或 828D 这类数控系统,直接上 Sinumerik OPC UA 客户端,省去中间转换环节。
1.3 方案整体架构与数据流向
整套方案的架构可以概括为“三层两网”:
- 设备层:各类传感器(温度、压力、流量、振动)、PLC(西门子 S7-1200/1500、三菱 FX 系列、欧姆龙 CP 系列)、智能仪表(E5CC 温控器、电表、流量计)。这一层输出的是 Modbus RTU/TCP 或 OPC UA 协议的数据。
- 边缘层:工业网关或工控机(推荐用 Ubuntu 或 Windows IoT),运行采集程序。Modbus 侧用轮询方式读取寄存器,OPC UA 侧用订阅方式接收数据变化。采集到的数据先写入本地 SQLite 或时序数据库做缓冲,然后通过 MQTT 发布到 Broker。
- 平台层:MQTT Broker(EMQX 或 Mosquitto)负责消息路由,后端服务订阅主题后写入时序数据库(InfluxDB、TDengine),再对接可视化大屏或 MES 系统。
两网指的是:OT 网络(设备层到边缘层,通常用 Modbus RTU 的 RS485 或 Modbus TCP 的工业以太网)和IT 网络(边缘层到平台层,走 MQTT over TCP/TLS)。两网之间通过网关做隔离,避免 IT 侧的流量风暴影响 OT 侧的实时性。
注意:OT 网络和 IT 网络一定要做物理或逻辑隔离。我见过一个项目,IT 侧有人跑了个网络扫描工具,直接把 Modbus RTU 总线上的轮询周期从 100ms 拖到了 2s,导致温控器失控。这个坑后面会详细说。
2. Modbus 协议深度解析与实操要点
2.1 Modbus RTU 报文结构与地址映射的坑
Modbus RTU 的报文格式看起来简单,但细节里全是坑。一帧完整的 RTU 报文包括:从站地址(1 字节)、功能码(1 字节)、数据域(N 字节)、CRC 校验(2 字节)。帧与帧之间靠 3.5 个字符时间的静默间隔来区分。
功能码常用的就几个:0x01 读线圈、0x02 读离散输入、0x03 读保持寄存器、0x04 读输入寄存器、0x05 写单个线圈、0x06 写单个寄存器、0x10 写多个寄存器。读传感器数据最常用的是 0x03 和 0x04。
地址映射是新手最容易翻车的地方。Modbus 协议文档里经常看到 40001、30001 这种地址,这是“PLC 地址”或“逻辑地址”,跟报文里实际传输的“协议地址”差一个偏移量。规则是:
| 地址类型 | 逻辑地址范围 | 协议地址范围 | 功能码 |
|---|---|---|---|
| 线圈 | 00001-09999 | 0000-9998 | 0x01 |
| 离散输入 | 10001-19999 | 0000-9998 | 0x02 |
| 输入寄存器 | 30001-39999 | 0000-9998 | 0x04 |
| 保持寄存器 | 40001-49999 | 0000-9998 | 0x03 |
也就是说,逻辑地址 40001 对应的协议地址是 0,40002 对应 1,以此类推。很多 Modbus Poll 的注册版本里直接填 40001 能读到数据,是因为软件帮你做了转换。但如果你自己写代码,就必须用协议地址。
实操心得:拿到一个 Modbus 设备,第一件事是确认它的地址是从 0 开始还是从 1 开始。有些国产仪表厂家文档写的是“寄存器地址 1”,实际协议地址就是 1;有些写的是“40001”,实际协议地址是 0。最稳妥的办法是用 Modbus Poll 或 Modbus Slave 先手动试读,确认地址偏移。
2.2 Modbus TCP 与 RTU 的差异及转换技巧
Modbus TCP 在 RTU 的基础上加了一个 7 字节的 MBAP 头(事务标识符 2 字节、协议标识符 2 字节、长度 2 字节、单元标识符 1 字节),去掉了 CRC 校验,因为 TCP 本身保证了数据完整性。
实际项目中经常遇到“设备只有 RTU 接口,但上位机只有网口”的情况。这时候有两种方案:一是用串口服务器(如有人 USR-N510、MOXA NPort)做 RTU over TCP 转换;二是用网关软件做协议转换。串口服务器的优点是稳定、免驱、配置简单,缺点是每个串口服务器只能接一条 RS485 总线,设备多了成本高。
如果现场有西门子 S7-1200/1500,可以用 CM 1241 RS485 模块扩展串口,然后在 PLC 里写 Modbus RTU 主站程序,再通过 OPC UA 或 S7 协议把数据吐出来。三菱 FX3U 的话,加 FX3U-485ADP-MB 模块,用 ADPRW 指令做 Modbus RTU 通讯,梯形图程序网上有很多模板可以参考。
2.3 用 Modbus Poll 和 Modbus Slave 快速验证通讯
调试 Modbus 的第一步永远是“先通再调”。我习惯用 Modbus Poll 做客户端、Modbus Slave 做服务端,在同一台电脑上模拟主从通讯,确认参数配置正确后再接真实设备。
具体步骤:
- 打开 Modbus Slave,设置从站地址为 1,功能码选 03(保持寄存器),起始地址填 0,数量填 10。
- 在数据区手动填入几个测试值,比如 100、200、300。
- 打开 Modbus Poll,连接方式选 Modbus TCP,IP 填 127.0.0.1,端口 502。
- 设置从站地址 1,功能码 03,起始地址 0,数量 10。
- 如果一切正常,Modbus Poll 的数据区会显示 Modbus Slave 里填的值。
这个流程看起来简单,但能帮你排除 90% 的配置问题。等模拟通了,再把 Modbus Poll 的 IP 改成真实设备的 IP,或者把连接方式改成 Serial Port 接 RS485 转换器。
注意:Modbus Poll 和 Modbus Slave 是商业软件,有 30 天试用期。网上流传的注册码和破解版本存在安全风险,建议公司采购正版授权,或者用开源的 QModMaster、pymodbus 替代。我自己写采集程序时,Python 环境下直接用 pymodbus 库,几行代码就能跑起来。
2.4 一主多从的轮询策略与超时处理
Modbus RTU 是严格的一主多从协议,总线上只能有一个主站。主站轮询多个从站时,必须等上一个从站响应或超时后才能发下一帧。如果某个从站掉线,主站不能死等,必须设置合理的超时时间。
我的经验值是:波特率 9600 时超时设 300ms,19200 时设 200ms,115200 时设 50ms。超时时间太短会导致正常从站被误判为掉线,太长则拖慢整个轮询周期。
假设总线上有 10 个从站,每个从站读 10 个寄存器,波特率 9600,每帧大约 25 字节。9600 波特率下,一个字节 10 位(1 起始位 + 8 数据位 + 1 停止位),传输 25 字节需要 25 × 10 / 9600 ≈ 26ms。加上从站处理时间和 3.5 字符间隔,单次轮询大约 40ms。10 个从站轮一遍就是 400ms。如果要求每个从站的数据刷新周期不超过 1s,这个配置是可行的;如果要求 200ms 刷新一次,就必须提高波特率或减少从站数量。
实操心得:轮询顺序也有讲究。把响应快的从站放在前面,响应慢的放在后面,可以减少总线空闲时间。另外,对于变化缓慢的模拟量(如温度),可以降低轮询频率;对于开关量或报警信号,提高轮询频率。这种差异化轮询策略能显著降低总线负载。
3. OPC UA 在工业数据采集中的落地实践
3.1 OPC UA 的信息模型为什么比 Modbus 香
Modbus 最大的问题是“数据没有语义”。你读到一个寄存器值是 235,它可能是 23.5°C,也可能是 2350rpm,还可能是 0x00EB 的位组合。每次对接新设备,都要翻手册、对地址、算量程,累且容易出错。
OPC UA 从根上解决了这个问题。它用地址空间(Address Space)来描述设备,每个节点(Node)有 NodeId、BrowseName、DisplayName、DataType、EngineeringUnits 等属性。客户端连上服务器后,可以浏览整个地址空间,自动发现有哪些变量、每个变量是什么类型、单位是什么。这就是所谓的“语义互操作”。
举个例子:西门子 S7-1500 的 OPC UA 服务器里,一个温度变量可能长这样:
- NodeId: ns=3;s="DB1"."Temperature"
- BrowseName: Temperature
- DataType: Float
- EngineeringUnits: °C
- AccessLevel: CurrentRead
客户端不需要任何手册,直接读这个节点就能拿到带单位的温度值。换一台设备,只要它支持 OPC UA,客户端代码几乎不用改。
3.2 用 UaExpert 快速浏览服务器节点
UaExpert 是 Unified Automation 出的免费 OPC UA 客户端,Windows 64 位版本目前是 v2.0.2 build 665。虽然界面不算现代,但功能齐全,是我调试 OPC UA 的首选工具。
连接步骤:
- 打开 UaExpert,右键 Servers → Add → Custom Discovery,输入 OPC UA 服务器的端点 URL,比如
opc.tcp://192.168.1.10:4840。 - 如果服务器启用了安全策略,需要选择对应的 Security Policy(None、Basic128Rsa15、Basic256Sha256 等)和认证方式(匿名、用户名密码、证书)。
- 连接成功后,左侧地址空间树会展开所有节点。找到你的目标变量,拖到中间的 Data Access View 里,就能实时看到数值变化。
- 右键变量可以查看属性,包括 DataType、EngineeringUnits、AccessLevel 等。
注意:西门子 S7-1500 的 OPC UA 服务器默认可能没开启,需要在 TIA Portal 里勾选“激活 OPC UA 服务器”,并设置端口号(默认 4840)和安全策略。另外,S7-1200 只有固件 V4.4 以上才支持 OPC UA 服务器功能,老固件只能做客户端。
3.3 Sinumerik OPC UA 客户端的特殊配置
数控机床的数据采集是个独立赛道。西门子 840D sl、828D、840D 这些系统自带 OPC UA 服务器,但节点结构和普通 PLC 不一样。你需要用 Sinumerik OPC UA 2.2 Client 或者直接通过 UaExpert 连接,浏览Sinumerik命名空间下的节点。
关键节点包括:
/Channel/Configuration/ChannelAxisName:通道轴名称/Channel/State/ActFeedRate:实际进给率/Channel/State/ActSpindleSpeed:实际主轴转速/Channel/ProgramInfo/ProgramName:当前程序名/Tool/State/ActToolId:当前刀具号
这些节点的 NodeId 通常是ns=2;s=/Channel/State/ActFeedRate这种形式。采集频率建议不要超过 10Hz,因为数控系统的 OPC UA 服务器资源有限,高频读取会影响加工实时性。
3.4 OPC UA 订阅模式与数据变化通知
OPC UA 有两种数据获取方式:Read(主动读取)和Subscription + MonitoredItem(订阅监控)。Read 适合低频、按需读取;订阅适合高频、持续采集。
订阅模式的工作流程:
- 客户端创建一个 Subscription,设置 PublishingInterval(发布间隔,比如 100ms)。
- 在 Subscription 下创建 MonitoredItem,指定要监控的 NodeId、SamplingInterval(采样间隔)、QueueSize(队列大小)。
- 服务器按照 SamplingInterval 采样,数据变化时放入队列,然后按照 PublishingInterval 打包发送给客户端。
- 客户端收到 Publish Response 后处理数据。
这种模式的好处是:客户端不需要轮询,服务器主动推送变化的数据,网络开销小,实时性高。但要注意 QueueSize 的设置——如果数据变化频率高于 PublishingInterval,队列会堆积,QueueSize 太小会丢数据,太大则占内存。一般设 10 左右比较合适。
实操心得:OPC UA 订阅的 SamplingInterval 不要设得比 PublishingInterval 还小,否则没意义。比如 PublishingInterval 是 100ms,SamplingInterval 设 50ms,服务器采样两次才发布一次,浪费资源。两者相等或 SamplingInterval 略大即可。
4. MQTT 协议详解与数据上云实操
4.1 MQTT 发布订阅模型与 QoS 等级选择
MQTT 的核心是发布订阅模型:发布者(Publisher)把消息发到某个主题(Topic),订阅者(Subscriber)订阅这个主题后就能收到消息。发布者和订阅者互不知道对方的存在,全靠 Broker 做路由。这种解耦设计特别适合工业场景——传感器只管发数据,云端服务只管收数据,中间加个 Broker 就能灵活扩展。
QoS(服务质量)是 MQTT 最关键的参数:
| QoS 等级 | 语义 | 报文交互 | 适用场景 |
|---|---|---|---|
| 0 | 最多一次 | PUBLISH | 高频传感器数据,丢一两帧无所谓 |
| 1 | 至少一次 | PUBLISH → PUBACK | 一般业务数据,允许重复但不能丢 |
| 2 | 恰好一次 | PUBLISH → PUBREC → PUBREL → PUBCOMP | 计费、报警等关键数据 |
工业采集里,温度、压力这类连续量用 QoS 0 就够了,丢一帧下一帧马上补上。但报警信号、累计产量这类数据必须用 QoS 1 或 2。QoS 2 的开销是 QoS 0 的 4 倍以上,非必要不用。
4.2 在 Windows 和 Linux 上搭建 MQTT Broker
Broker 是 MQTT 的心脏。生产环境推荐 EMQX(功能全、性能强、有 Web 管理界面),测试环境用 Mosquitto(轻量、配置简单)就够了。
Windows 上把 Mosquitto 设成本地服务:
- 从 Mosquitto 官网下载 Windows 安装包,安装到
C:\Program Files\mosquitto。 - 编辑
mosquitto.conf,添加listener 1883和allow_anonymous true(测试用,生产环境要关掉匿名)。 - 以管理员身份打开 CMD,执行:
sc create mosquitto binPath= "C:\Program Files\mosquitto\mosquitto.exe -c \"C:\Program Files\mosquitto\mosquitto.conf\"" start= auto - 执行
sc start mosquitto启动服务。之后开机自动运行,不用手动开窗口。
Linux(Ubuntu)上安装 Mosquitto:
sudo apt update sudo apt install mosquitto mosquitto-clients sudo systemctl enable mosquitto sudo systemctl start mosquitto如果服务器不能联网,需要离线安装。下载.deb包后,用dpkg -i安装,依赖缺失的话用apt-get install -f补齐。
注意:生产环境的 MQTT Broker 一定要开启认证和 TLS。匿名访问的 Broker 暴露在公网上,分分钟被扫描到并滥用。EMQX 支持用户名密码、JWT、LDAP 等多种认证方式,配置也不复杂。
4.3 MQTT 主题设计与消息格式规范
主题设计是 MQTT 落地最容易忽视的环节。好的主题结构应该具备:层次清晰、可扩展、易订阅。
我常用的主题模板:
factory/{车间编号}/{产线编号}/{设备编号}/{数据类型}例如:
factory/A1/line01/injection01/temperaturefactory/A1/line01/injection01/pressurefactory/A1/line01/injection01/status
订阅时可以用通配符:
factory/A1/line01/+/temperature:订阅 A1 车间 line01 产线所有设备的温度factory/A1/#:订阅 A1 车间所有数据
消息格式推荐 JSON,可读性好,扩展方便:
{ "deviceId": "injection01", "timestamp": 1712345678000, "temperature": 235.6, "unit": "°C", "quality": "good" }如果带宽紧张,可以用 MessagePack 或 Protobuf 做二进制序列化,体积能压缩到 JSON 的 30% 左右。但调试难度增加,建议只在数据量特别大时使用。
4.4 用 MQTT Explorer 和命令行验证消息收发
MQTT Explorer 是调试 MQTT 的神器,图形界面,能实时显示主题树和消息内容。下载安装后,新建连接,填 Broker 地址和端口,如果开了认证就填用户名密码。
连接成功后,左侧是主题树,右侧是消息列表。你可以直接在顶部输入主题发布消息,也可以订阅通配符查看所有消息。
命令行验证用 mosquitto_pub 和 mosquitto_sub:
# 订阅所有主题 mosquitto_sub -h 192.168.1.100 -t "#" -v # 发布一条消息 mosquitto_pub -h 192.168.1.100 -t "factory/A1/line01/injection01/temperature" -m '{"value":235.6}'如果 Broker 开了认证,加-u username -P password。开了 TLS 的话,加--cafile ca.crt。
实操心得:调试 MQTT 时,先用
#订阅所有主题,确认消息能正常收发。然后再逐步缩小主题范围,排查是发布端问题还是订阅端问题。另外,MQTT Explorer 的消息缓存有限,高频消息会刷屏,建议在 Broker 端做限流或降采样。
5. 边缘采集程序的架构与核心代码实现
5.1 采集程序的模块划分与线程模型
一个稳定的边缘采集程序,不能把所有逻辑塞在一个循环里。我的做法是分成四个模块,每个模块独立线程:
- 采集线程:负责 Modbus 轮询和 OPC UA 订阅回调,把原始数据写入内部队列。
- 处理线程:从队列取数据,做量程转换、单位换算、异常值过滤,然后写入本地缓冲。
- 发布线程:从本地缓冲读取数据,通过 MQTT 发布到 Broker。
- 看门狗线程:监控各线程状态,检测到卡死或异常时重启对应线程。
线程之间用queue.Queue或collections.deque做数据传递,避免共享内存加锁的复杂性。Python 的 GIL 对 I/O 密集型任务影响不大,采集程序大部分时间在等网络或串口,所以 Python 完全够用。如果采集频率要求极高(比如 1kHz 以上),建议用 Go 或 C++ 重写。
5.2 用 pymodbus 实现 Modbus RTU/TCP 采集
pymodbus 是 Python 下最成熟的 Modbus 库,支持 RTU、TCP、ASCII 三种模式。下面是一个 RTU 采集的示例:
from pymodbus.client import ModbusSerialClient import time client = ModbusSerialClient( port='COM3', baudrate=9600, parity='N', stopbits=1, bytesize=8, timeout=0.3 ) client.connect() while True: try: # 读从站1的保持寄存器,起始地址0,数量10 result = client.read_holding_registers(address=0, count=10, slave=1) if not result.isError(): print(f"寄存器值: {result.registers}") else: print(f"读取错误: {result}") except Exception as e: print(f"通讯异常: {e}") time.sleep(0.1)TCP 模式只需把ModbusSerialClient换成ModbusTcpClient,参数改成host和port。
注意:pymodbus 3.x 版本的 API 跟 2.x 有较大变化,
slave参数在 3.x 里改成了slave或unit,具体看版本。建议锁定版本号,避免升级后代码跑不起来。
5.3 用 asyncua 实现 OPC UA 客户端订阅
asyncua 是 Python 下的异步 OPC UA 库,基于 asyncio,适合做订阅采集:
import asyncio from asyncua import Client async def main(): async with Client(url="opc.tcp://192.168.1.10:4840") as client: # 获取节点 node = client.get_node("ns=3;s=\"DB1\".\"Temperature\"") # 创建订阅 handler = SubscriptionHandler() subscription = await client.create_subscription(100, handler) # 订阅节点 await subscription.subscribe_data_change(node) # 持续运行 while True: await asyncio.sleep(1) class SubscriptionHandler: def datachange_notification(self, node, val, data): print(f"节点 {node} 的值变为: {val}") asyncio.run(main())这个模式比轮询高效得多,服务器只在数据变化时推送,网络开销极小。
5.4 用 paho-mqtt 发布数据并保证不丢消息
paho-mqtt 是 Python 下最常用的 MQTT 客户端库。要保证消息不丢,关键是设置 QoS 1 或 2,并处理on_disconnect回调做重连:
import paho.mqtt.client as mqtt import json import time client = mqtt.Client(client_id="edge_gateway_01") client.username_pw_set("user", "password") def on_connect(client, userdata, flags, rc): print(f"连接成功,返回码: {rc}") def on_disconnect(client, userdata, rc): print(f"连接断开,返回码: {rc},尝试重连...") while True: try: client.reconnect() break except Exception: time.sleep(5) client.on_connect = on_connect client.on_disconnect = on_disconnect client.connect("192.168.1.100", 1883, 60) client.loop_start() # 发布数据 data = {"deviceId": "sensor01", "temperature": 235.6, "timestamp": int(time.time() * 1000)} client.publish("factory/A1/line01/sensor01/temperature", json.dumps(data), qos=1)QoS 1 保证消息至少到达一次,配合本地缓冲,即使网络短暂中断,恢复后也能把积压的数据补发出去。
实操心得:
client.loop_start()会启动一个后台线程处理网络收发,主线程可以继续做其他事。但要注意,paho-mqtt 的publish是异步的,消息先进入内部队列,实际发送由后台线程完成。如果程序退出太快,队列里的消息可能还没发出去。稳妥的做法是在退出前调用client.loop_stop()并等待队列清空。
6. 常见问题与排查技巧实录
6.1 Modbus 通讯异常排查速查表
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 完全无响应 | 接线错误、波特率不匹配、从站地址错误 | 检查 A/B 线是否接反,用示波器看波形,确认从站地址 |
| 返回 Exception Response | 功能码不支持、寄存器地址越界 | 查看异常码:01 非法功能、02 非法地址、03 非法数据值 |
| 数据乱码 | 字节序错误、数据类型不匹配 | 尝试大小端切换,确认是整数还是浮点数 |
| 偶发超时 | 总线干扰、终端电阻缺失 | 加 120Ω 终端电阻,检查屏蔽线接地 |
| 多从站冲突 | 从站地址重复、轮询顺序不当 | 逐个断开从站排查,调整轮询顺序 |
6.2 OPC UA 连接失败与证书问题
OPC UA 的安全机制是双刃剑,配置不当会导致连接失败。常见问题:
- 证书不受信任:客户端和服务器首次连接时,需要交换证书并加入信任列表。UaExpert 会弹出证书确认对话框,点击“Trust”即可。自己写代码时,需要把服务器证书加入客户端的信任库。
- 安全策略不匹配:服务器只支持 Basic256Sha256,客户端却选了 None,直接拒绝。解决方法是先浏览服务器的端点列表,选择匹配的策略。
- 端口被占用:4840 是 OPC UA 默认端口,如果被其他程序占用,服务器启动会失败。用
netstat -ano | findstr 4840排查。
6.3 MQTT 消息丢失的三种场景与对策
场景一:QoS 0 且网络抖动。QoS 0 不保证送达,网络一抖就丢。对策:关键数据用 QoS 1。
场景二:Broker 重启导致会话丢失。客户端连接时如果clean_session=True,Broker 重启后会话被清除,离线期间的消息全丢。对策:设置clean_session=False,并给客户端一个固定的 ClientId。
场景三:发布频率超过 Broker 处理能力。EMQX 单节点能处理几十万 QPS,但配置低的 Mosquitto 可能几千就顶不住了。对策:在边缘侧做降采样或批量发送,降低消息频率。
6.4 边缘网关断电断网后的数据补传
工业现场断电断网是常态,采集程序必须能扛住。我的做法是:
- 采集到的数据先写入本地 SQLite 数据库,带一个
uploaded标志位。 - 发布线程从数据库读取
uploaded=0的记录,发布成功后更新标志位。 - 网络恢复后,发布线程自动把积压的数据补发出去。
- 数据库设置容量上限(比如 10 万条),超过后删除最旧的已上传记录。
这套机制实测能扛住 24 小时以上的断网,数据零丢失。SQLite 的写入性能足够,单表 10 万条记录下,插入和查询都在毫秒级。
实操心得:SQLite 默认的
synchronous=FULL模式每次写入都刷盘,速度慢。改成synchronous=NORMAL或WAL模式,性能提升明显,断电时最多丢最后几条记录。对于工业采集,这个取舍是值得的。
6.5 采集程序性能优化的几个实用技巧
- 批量读取:Modbus 连续寄存器一次读 100 个,比读 10 次 10 个快得多。但要注意从站的最大读取长度限制,有些设备只支持 32 个寄存器。
- 异步 I/O:OPC UA 和 MQTT 都用异步库,避免阻塞主线程。
- 连接池:如果采集多个 Modbus TCP 设备,维护一个连接池,避免频繁建连断连。
- 数据压缩:MQTT 消息体用 gzip 压缩,能减少 60% 以上的带宽。
- 日志分级:生产环境只记录 WARNING 以上日志,DEBUG 日志写入文件并定期清理,避免磁盘写满。
这套方案我在三个工厂落地过,从单条产线到整车间覆盖,最长的稳定运行了两年多。核心体会是:协议选型要务实,别追求新技术;边缘缓冲必须有,别信网络永远可靠;调试工具要顺手,Modbus Poll、UaExpert、MQTT Explorer 这三个装好,能省一半时间。