尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

工业物联网双协议实战:SNMP采集与MQTT传输的网关架构与部署

工业物联网双协议实战:SNMP采集与MQTT传输的网关架构与部署 工业现场的设备管理有个很尴尬的现实车间里跑的PLC、电表、水表、传感器十台里有八台只认SNMP或者Modbus这类老派协议而老板和运维团队又希望数据能实时推到手机、大屏、云端看板。这两拨需求中间隔着一道鸿沟——传统工业协议擅长管设备现代物联网协议擅长传数据。我做过好几个工厂的采集项目最后跑通的那套方案基本都是MQTT加SNMP双协议组合SNMP负责把设备底层的状态、告警、流量、温度这些指标捞出来MQTT负责把这些数据高效、可靠地送到上层平台。这套组合不是拍脑袋想出来的是被现场逼出来的。1. 为什么工业设备管理绕不开SNMP和MQTT这两条腿1.1 SNMP在工业现场的不可替代性先说SNMP。很多人觉得这协议老掉牙了但你去任何一个机房、变电站、水厂泵站看一眼交换机、路由器、UPS、工业网关、部分PLC出厂就带SNMP Agent。它最大的价值在于标准化——MIB管理信息库是一套全球统一的命名体系OID对象标识符就像设备的身份证号1.3.6.1.2.1.1.1.0这个OID在华为交换机上代表系统描述在思科交换机上也代表系统描述。这意味着你写一套采集逻辑换品牌换型号大部分OID还能复用。SNMP有三个核心操作GET读单个值、GETNEXT/GETBULK遍历表格、TRAP/INFORM设备主动上报告警。工业场景里最常用的是轮询GET和接收TRAP。轮询适合采集周期性指标比如CPU利用率、端口流量、温度TRAP适合捕捉突发事件比如端口down、电源故障、阈值越限。我见过不少项目只做轮询不做TRAP结果设备都烧了半小时了平台还在等下一个采集周期这就是没吃透SNMP的典型表现。SNMP目前主流是v2c和v3两个版本。v2c用community字符串做认证配置简单但安全性弱v3支持用户认证和加密适合对安全有要求的场景。工业内网里v2c用得最多因为设备老、配置省事但如果你的采集网关要跨网段或者上云v3是必须的。这里有个坑很多老设备的SNMP v3实现不完整只支持认证不支持加密对接的时候要先用snmpwalk测一遍别上来就写代码。1.2 MQTT解决的是最后一公里的传输问题SNMP采集到的数据怎么送到云端或者中心平台传统做法是采集程序直接写数据库或者用Modbus TCP转发但这两种方式在分布式、弱网、多节点场景下都很吃力。MQTT的优势就体现出来了发布订阅模型、轻量级头部、支持QoS等级、断线重连、遗嘱消息。一个采集网关可以同时向多个主题发布数据云端、边缘节点、手机App各自订阅自己关心的主题互不干扰。MQTT的QoS等级是工业场景必须搞清楚的QoS 0是最多一次发出去就不管了适合高频但允许丢的指标QoS 1是至少一次有确认机制但可能重复适合大多数设备状态数据QoS 2是恰好一次四次握手开销大工业场景用得少。我一般建议设备状态和告警用QoS 1高频传感器数据用QoS 0加本地缓存别一股脑全上QoS 2broker扛不住。还有一个容易被忽略的点是遗嘱消息Last Will and Testament。采集网关连上broker时预先注册一条遗嘱一旦网关异常断线broker会自动发布这条消息云端立刻知道这个网关掉线了。这比等心跳超时再判断要快得多在无人值守的泵站、基站场景里特别有用。1.3 双协议组合的分工逻辑把这两个协议放在一起分工其实很清晰层次协议职责典型数据设备层SNMP采集设备指标、接收告警CPU、内存、端口流量、温度、电源状态传输层MQTT数据上报、指令下发JSON/二进制 payload、主题消息平台层MQTT Broker消息路由、订阅分发主题树、QoS策略、保留消息采集网关是这两层的粘合剂它作为SNMP Manager去轮询或接收设备数据同时作为MQTT Client把数据发布到broker。这个网关可以是一台工控机、一个树莓派、一台ARM边缘盒子甚至是一台跑Linux的旧服务器。关键是它要能同时跑SNMP采集进程和MQTT客户端进程并且有本地缓存能力断网时不丢数据。2. 采集网关的选型与SNMP采集链路搭建2.1 网关硬件和系统的实际选择采集网关的选型直接决定项目能不能落地。我按场景分三类说轻量场景几十个SNMP节点、数据频率低树莓派4B或者类似的ARM开发板就够了跑Debian或者Ubuntu Server装net-snmp工具和Python的pysnmp库。成本低功耗小适合水表采集器、小型泵站这类场景。注意ARM板子的网口数量很多只有一个要接多个网段得加USB网卡或者用VLAN。中等场景几百个节点、需要本地存储和边缘计算工控机或者ARM边缘计算盒子比如瑞芯微、全志的方案跑Ubuntu或者麒麟系统。这类设备通常有多个网口、RS485、DI/DO能同时接SNMP设备和Modbus设备。麒麟V10在ARM上的离线安装是个常见需求后面单独说。重载场景上千节点、高频率采集x86工控机或者虚拟机跑CentOS/Ubuntu用Go或者Java写采集程序配合时序数据库做本地缓存。这种场景下SNMP轮询的并发调度是瓶颈需要做分片和错峰。系统层面我强烈建议用Linux而不是Windows。net-snmp在Linux上的稳定性和工具链完整度远超Windowssnmpwalk、snmpget、snmptrap这些命令行工具调试起来非常方便。Windows上虽然也有SNMP服务但配置繁琐而且很多工业网关默认就是Linux。2.2 SNMP采集的核心参数配置SNMP采集有几个参数必须调对否则要么采不到要么把设备采挂超时时间timeout默认1秒工业设备响应慢建议设2-3秒。太短会大量超时太长会拖慢轮询周期。重试次数retries默认5次建议设1-2次。重试太多会在设备繁忙时雪上加霜。轮询间隔状态类指标30-60秒流量类指标10-30秒别低于5秒很多老设备扛不住。并发数单进程并发别超过50超过就要分片。SNMP是UDP丢包是常态并发太高丢包率飙升。用snmpwalk测试的时候命令大概是这样# SNMP v2c 遍历系统信息 snmpwalk -v 2c -c public -t 3 -r 2 192.168.1.100 1.3.6.1.2.1.1 # SNMP v3 带认证 snmpwalk -v 3 -l authNoPriv -u monitor -a MD5 -A authpass -t 3 -r 2 192.168.1.100 1.3.6.1.2.1.1 # 获取单个OID snmpget -v 2c -c public -t 3 -r 2 192.168.1.100 1.3.6.1.2.1.1.3.0这里有个实操经验先用snmpwalk把设备的MIB树完整走一遍把关心的OID记下来再写采集程序。别对着厂商文档抄OID文档和实际固件经常对不上。我遇到过一个交换机文档说端口流量在1.3.6.1.2.1.2.2.1.10实际固件里这个OID返回的是空值真正的流量在厂商私有MIB里。2.3 用Python搭建SNMP采集端Python做SNMP采集pysnmp是主流库。下面是一个最小可用的采集示例采集系统描述和运行时间from pysnmp.hlapi import * def snmp_get(ip, community, oid): iterator getCmd( SnmpEngine(), CommunityData(community, mpModel1), # mpModel1 表示 v2c UdpTransportTarget((ip, 161), timeout3, retries2), ContextData(), ObjectType(ObjectIdentity(oid)) ) errorIndication, errorStatus, errorIndex, varBinds next(iterator) if errorIndication: return None elif errorStatus: return None else: for varBind in varBinds: return varBind[1].prettyPrint() # 采集系统描述 sys_descr snmp_get(192.168.1.100, public, 1.3.6.1.2.1.1.1.0) print(sys_descr)这个写法适合低频采集。如果要高频轮询多个OID建议用bulkCmd批量获取减少往返次数。另外pysnmp是纯Python实现性能一般节点多了要上多进程或者换net-snmp的Python绑定。对于TRAP接收需要起一个SNMP Trap Receiver进程监听162端口。pysnmp也支持但生产环境我更推荐用snmptrapdnet-snmp自带的守护进程配置好之后把TRAP转成脚本调用或者写入队列稳定性更好。3. MQTT主题设计与消息可靠性保障3.1 主题树的设计原则MQTT主题设计是双协议组合里最容易被做烂的部分。我见过有人把所有数据往一个主题里塞结果订阅端要处理几十种payload格式维护成本爆炸。好的主题树应该满足三个条件层次清晰、可通配订阅、与设备拓扑对应。推荐的主题结构factory/{厂区}/{车间}/{设备类型}/{设备ID}/{数据类型}举个例子factory/A/workshop1/switch/SW001/status factory/A/workshop1/switch/SW001/traffic factory/A/workshop1/ups/UPS003/alarm factory/A/workshop2/plc/PLC007/temperature这样设计的好处是云端可以按厂区订阅factory/A/#按设备类型订阅factory///switch/#按告警订阅factory/////alarm。通配符匹配单层#匹配多层用好了能省很多订阅逻辑。注意主题里不要用中文、空格、特殊字符虽然MQTT协议允许但很多broker和客户端处理起来会出问题。设备ID用英文数字组合分隔符统一用斜杠。3.2 QoS和保留消息的取舍QoS的选择前面提过这里补充一个实际场景的决策表数据类型推荐QoS是否保留消息理由设备在线状态1是新订阅者需要立刻知道当前状态实时告警1否告警有时效性历史告警走数据库周期性指标0否高频数据丢一两个点可接受配置下发1否需要确认送达设备心跳0是保留最后心跳判断在线状态保留消息Retained Message是个双刃剑。它让新订阅者立刻拿到最后一条消息但如果设备状态变了而保留消息没更新就会误导订阅者。我的做法是状态类主题用保留消息数据类主题不用。3.3 消息不丢失的完整链路MQTT怎么保证不丢失消息至少一次是个高频问题。光靠QoS 1不够要从采集端到broker到订阅端全链路考虑采集端SNMP采集到的数据先写本地队列比如Redis、SQLite、或者内存队列MQTT发布成功后再出队。断网时数据堆在队列里恢复后补发。队列要有上限和淘汰策略别把磁盘写满。传输端QoS 1保证broker收到消息但broker到订阅端的投递也要QoS 1。如果订阅端处理慢broker的消息队列会堆积要设置合理的队列长度和丢弃策略。订阅端收到消息后先落库再确认别先确认再处理。处理失败要有重试和死信队列。Broker端选型很关键。EMQX、Mosquitto、HiveMQ、VerneMQ各有特点。中小规模用Mosquitto够用大规模集群用EMQX。Broker要开启持久化防止重启丢消息。这里有个我踩过的坑Mosquitto默认的max_queued_messages是1000超过就丢。工业场景下订阅端偶尔卡顿1000条很快就满了。要改成10000以上并且监控队列长度。4. 双协议网关的代码实现与联调4.1 采集与发布的整体架构一个完整的双协议网关内部结构大概是这样[SNMP设备] --SNMP-- [采集模块] --队列-- [MQTT发布模块] --MQTT-- [Broker] | [本地缓存] | [TRAP接收]采集模块负责轮询和TRAP接收把数据标准化成统一格式推荐JSON写入本地队列。发布模块从队列取数据按主题规则发布到broker。本地缓存用于断网续传和TRAP暂存。数据格式建议统一成{ device_id: SW001, device_type: switch, timestamp: 1700000000, metrics: { cpu_usage: 45, mem_usage: 62, port1_in: 1024000, port1_out: 2048000 }, alarm: null }告警数据单独一个格式{ device_id: UPS003, device_type: ups, timestamp: 1700000000, alarm: { level: critical, code: POWER_LOSS, message: 市电中断切换电池供电 } }4.2 用Python把SNMP数据推到MQTT下面是一个简化但可运行的示例把SNMP采集和MQTT发布串起来import json import time import paho.mqtt.client as mqtt from pysnmp.hlapi import * # MQTT配置 MQTT_BROKER 192.168.1.200 MQTT_PORT 1883 MQTT_USER gateway MQTT_PASS gateway123 TOPIC_PREFIX factory/A/workshop1 # SNMP设备列表 DEVICES [ {ip: 192.168.1.100, community: public, id: SW001, type: switch}, {ip: 192.168.1.101, community: public, id: UPS003, type: ups}, ] # 关心的OID OIDS { sys_descr: 1.3.6.1.2.1.1.1.0, sys_uptime: 1.3.6.1.2.1.1.3.0, cpu_usage: 1.3.6.1.4.1.2021.11.11.0, } def snmp_get(ip, community, oid): iterator getCmd( SnmpEngine(), CommunityData(community, mpModel1), UdpTransportTarget((ip, 161), timeout3, retries2), ContextData(), ObjectType(ObjectIdentity(oid)) ) errorIndication, errorStatus, errorIndex, varBinds next(iterator) if errorIndication or errorStatus: return None for varBind in varBinds: return varBind[1].prettyPrint() def on_connect(client, userdata, flags, rc): print(MQTT connected with result code, rc) client mqtt.Client(client_idgateway_001) client.username_pw_set(MQTT_USER, MQTT_PASS) client.will_set(f{TOPIC_PREFIX}/gateway/gateway_001/status, offline, qos1, retainTrue) client.on_connect on_connect client.connect(MQTT_BROKER, MQTT_PORT, 60) client.loop_start() client.publish(f{TOPIC_PREFIX}/gateway/gateway_001/status, online, qos1, retainTrue) while True: for dev in DEVICES: metrics {} for name, oid in OIDS.items(): val snmp_get(dev[ip], dev[community], oid) if val is not None: metrics[name] val if metrics: payload { device_id: dev[id], device_type: dev[type], timestamp: int(time.time()), metrics: metrics } topic f{TOPIC_PREFIX}/{dev[type]}/{dev[id]}/status client.publish(topic, json.dumps(payload), qos1) time.sleep(30)这个代码有几个地方值得说will_set注册了遗嘱消息网关掉线时broker会自动发offlineloop_start启动了后台网络线程主线程只管采集发布用QoS 1保证送达。生产环境还要加异常处理、本地队列、日志、配置热加载但骨架就是这样。4.3 联调时最容易卡住的几个点第一个坑SNMP community不对或者被ACL拦了。现象是snmpwalk超时但ping得通。先确认community字符串再确认设备有没有配SNMP访问控制列表。很多交换机默认只允许特定网段访问SNMP。第二个坑MQTT broker认证失败但客户端不报错。paho-mqtt的connect是异步的认证失败在on_connect回调的rc里。要打印rcrc5表示未授权。别以为connect没抛异常就是成功了。第三个坑主题发布成功但订阅端收不到。检查订阅端的主题过滤器factory/A/#能匹配factory/A/workshop1/switch/SW001/status但factory/A/只能匹配一层。还有broker的ACL有些broker配置了主题级别的发布订阅权限。第四个坑中文payload乱码。MQTT payload是二进制的中文要UTF-8编码。paho-mqtt的publish如果传str会自动编码但有些客户端不会。统一用json.dumps(..., ensure_asciiFalse).encode(utf-8)。5. 工业现场的部署细节与踩坑记录5.1 麒麟V10 ARM离线安装MQTT客户端国产化项目里麒麟V10 ARM系统上离线装MQTT客户端是个高频需求。现场往往没有外网只能用离线包。以paho-mqtt为例步骤是在一台能联网的同架构机器上下载wheel包pip download paho-mqtt -d ./pkgs --platform manylinux2014_aarch64 --only-binary:all:把pkgs目录拷到目标机器目标机器上执行pip install --no-index --find-links./pkgs paho-mqtt如果目标机器连pip都没有就要用系统包管理器。麒麟V10基于CentOS/RHEL系可以用rpm -ivh装python3-paho-mqtt的rpm包。但很多情况下没有现成rpm就得从源码装。源码装paho-mqtt其实不需要编译纯Python包解压后python3 setup.py install就行。net-snmp在麒麟上的离线安装稍微麻烦点依赖libssl、libcrypto等。建议用yumdownloader把依赖一起下下来做成离线repo用createrepo建本地源这样装起来最省事。5.2 水表采集器的双协议适配水表采集器是个典型场景采集器通过Modbus 645或者Modbus RTU读水表数据然后通过MQTT上报。但有些采集器本身也支持SNMP用于被网管平台管理。这种设备就是双协议组合的天然载体。对接水表采集器时要注意Modbus 645和Modbus RTU的寄存器地址不一样645是表号数据标识RTU是寄存器地址。采集器一般会做转换但转换规则要跟厂商确认。MQTT上报的payload格式也要确认有些采集器用私有格式有些用JSON。如果采集器只支持Modbus不支持MQTT那就在网关上做转换网关用Modbus读采集器转成MQTT发布。这就是协议转换网关的典型用法。5.3 现场部署的几条硬经验网络隔离SNMP采集网段和MQTT上报网段最好分开或者用VLAN隔离。SNMP轮询的广播包和MQTT的TCP连接混在一起网络质量差的时候互相影响。时间同步所有设备、网关、broker都要NTP对时。SNMP的sysUptime是相对时间MQTT的timestamp是绝对时间时间不同步会导致数据对不上。我遇到过一次网关时间慢了8小时告警时间全错排查了半天。日志和监控网关要记录SNMP采集成功率、MQTT发布成功率、队列长度。这些指标本身也可以通过MQTT上报形成自监控。采集成功率低于95%就要查网络或设备。电源和看门狗工业现场电压不稳网关要配UPS或者宽压电源。软件层面加看门狗进程挂了自动重启。systemd的Restartalways是最简单的方案。固件和MIB版本设备固件升级后MIB可能变OID可能失效。升级前要备份MIB升级后要重新验证OID。这个坑我在一个变电站项目里踩过升级后一半的OID返回空值最后是厂商改了私有MIB。6. 从单点采集到规模化管理的演进思路6.1 采集节点的分片与调度设备数量上去之后单网关扛不住要做分片。分片策略有几种按网段分、按设备类型分、按地理区域分。我一般按网段分因为SNMP轮询的瓶颈在网络IO同网段的设备放一个网关减少跨网段流量。分片之后要有统一的配置管理。每个网关的采集配置设备列表、OID映射、主题前缀不能硬编码要从配置中心拉取。配置中心可以用MQTT的保留消息实现网关订阅config/gateway/{gateway_id}配置变更时发布新配置网关收到后热加载。6.2 数据质量与告警收敛双协议组合跑起来之后数据量会很大告警也会很多。要做告警收敛同一设备的同类告警在时间窗口内只发一次恢复时发恢复消息。SNMP TRAP本身有重复上报的问题要在网关侧去重。数据质量方面SNMP采集失败要标记为null而不是0否则会污染统计。MQTT发布失败要重试重试失败要落盘。这些细节决定了数据能不能用于生产决策。6.3 边缘计算与云端协同网关不只是转发还可以做边缘计算。比如在网关侧计算设备的健康度、做阈值判断、聚合多个设备的数据。这样云端收到的就是加工过的数据减少传输量和云端计算压力。云端则负责全局视图跨厂区对比、历史趋势、机器学习预测。MQTT的主题设计要支持这种分层边缘算完的结果发到factory/A/aggregated/#原始数据发到factory/A/raw/#云端按需订阅。这套双协议组合我用了几年从几十个设备的小项目到上千个设备的厂区都跑过。核心经验就一条SNMP负责把设备管好MQTT负责把数据传好中间的网关要足够健壮。网关的健壮性体现在队列、重试、缓存、监控这些不性感的地方但这些地方做不好再漂亮的架构都是空中楼阁。
返回列表