☰
工业物联网双协议实战:SNMP采集与MQTT传输的网关架构与部署
2026/9/30 0:54:26 网站建设 项目流程

工业现场的设备管理有个很尴尬的现实:车间里跑的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 2,broker扛不住。

还有一个容易被忽略的点是遗嘱消息(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上的稳定性和工具链完整度远超Windows,snmpwalk、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, mpModel=1), # mpModel=1 表示 v2c UdpTransportTarget((ip, 161), timeout=3, retries=2), 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也支持,但生产环境我更推荐用snmptrapd(net-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, mpModel=1), UdpTransportTarget((ip, 161), timeout=3, retries=2), 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_id="gateway_001") client.username_pw_set(MQTT_USER, MQTT_PASS) client.will_set(f"{TOPIC_PREFIX}/gateway/gateway_001/status", "offline", qos=1, retain=True) 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", qos=1, retain=True) 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), qos=1) time.sleep(30)

这个代码有几个地方值得说:will_set注册了遗嘱消息,网关掉线时broker会自动发offline;loop_start启动了后台网络线程,主线程只管采集;发布用QoS 1保证送达。生产环境还要加异常处理、本地队列、日志、配置热加载,但骨架就是这样。

4.3 联调时最容易卡住的几个点

第一个坑:SNMP community不对或者被ACL拦了。现象是snmpwalk超时,但ping得通。先确认community字符串,再确认设备有没有配SNMP访问控制列表。很多交换机默认只允许特定网段访问SNMP。

第二个坑:MQTT broker认证失败但客户端不报错。paho-mqtt的connect是异步的,认证失败在on_connect回调的rc里。要打印rc,rc=5表示未授权。别以为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_ascii=False).encode('utf-8')。

5. 工业现场的部署细节与踩坑记录

5.1 麒麟V10 ARM离线安装MQTT客户端

国产化项目里,麒麟V10 ARM系统上离线装MQTT客户端是个高频需求。现场往往没有外网,只能用离线包。以paho-mqtt为例,步骤是:

  1. 在一台能联网的同架构机器上下载wheel包:pip download paho-mqtt -d ./pkgs --platform manylinux2014_aarch64 --only-binary=:all:
  2. 把pkgs目录拷到目标机器
  3. 目标机器上执行: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的Restart=always是最简单的方案。

固件和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负责把数据传好,中间的网关要足够健壮。网关的健壮性体现在队列、重试、缓存、监控这些"不性感"的地方,但这些地方做不好,再漂亮的架构都是空中楼阁。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询