ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

工业设备管理双协议实战:SNMP与MQTT组合采集与协同

工业设备管理双协议实战:SNMP与MQTT组合采集与协同 1. 工业设备管理为什么需要双协议组合1.1 从两个真实场景说起先聊两个我亲身经历的场景。第一个场景某汽车零部件工厂的冲压车间现场有12台不同年份采购的冲压机。最早那批2012年上的设备控制器只支持SNMP网管能读到CPU负载、内存占用、网口流量这些指标但读不到模具温度、液压油压这类工艺数据。后来2019年新增的设备倒是支持MQTT能把工艺数据实时推上来但设备本身的硬件健康状态又只能通过SNMP轮询获取。结果就是运维团队每天要在两套系统之间来回切换一套看网络健康一套看工艺状态中间没有任何关联。第二个场景一个做智慧园区的项目底下有大量的电表、水表、环境传感器这些设备通过RS-485总线连到边缘网关网关再往上走。网关本身支持SNMP可以被网管平台纳管但业务侧需要的是MQTT的发布订阅模型因为要对接多个上层应用每个应用订阅自己关心的主题。如果只走SNMP每加一个应用就要改一次网管配置扩展性极差。这两个场景指向同一个结论工业设备管理不是选MQTT还是选SNMP的问题而是两者各管一段、组合使用的问题。SNMP管“设备本身好不好”MQTT管“设备在干什么”。前者是基础设施层的健康监控后者是业务数据层的实时流转。1.2 两种协议的本质差异要理解为什么组合使用是最佳实践得先把两者的基因说清楚。SNMP简单网络管理协议诞生于1988年设计初衷就是让网管系统能远程读取和设置网络设备的状态。它的核心模型是Manager-AgentManager主动去轮询AgentAgent被动响应。这个模型决定了SNMP天然适合做周期性状态采集比如每30秒读一次设备的CPU、内存、接口状态。它的优势在于标准化程度极高几乎所有企业级网络设备、工控设备都内置了SNMP Agent不需要额外开发。MQTT消息队列遥测传输协议诞生于1999年设计初衷是在低带宽、不稳定网络环境下做可靠的消息传输。它的核心模型是发布-订阅设备作为Publisher把消息发到Broker应用作为Subscriber从Broker订阅自己关心的主题。这个模型决定了MQTT天然适合做事件驱动的数据流转比如设备温度超过阈值时立即推送告警而不是等下一次轮询。用一个生活化的类比SNMP像是你定期给设备打电话问“你还好吗”MQTT像是设备主动给你发微信说“我这边有情况”。打电话的方式你能控制节奏但实时性差发微信的方式实时性好但你得提前约定好发什么内容、发给谁。1.3 组合使用的核心价值把两者组合起来价值体现在三个层面。第一层数据互补。SNMP提供的是设备硬件层面的指标——CPU、内存、磁盘、网口流量、电源状态、风扇转速。MQTT提供的是业务层面的数据——传感器读数、工艺参数、告警事件、设备运行状态。两者叠加才能形成完整的设备画像。第二层通道冗余。在实际工业现场网络环境往往很复杂。SNMP走UDP 161端口MQTT走TCP 1883/8883端口。当网络出现波动时两种协议的容错表现不同。UDP在丢包时不会重传适合高频轮询TCP有重传机制适合可靠传输。组合使用相当于给数据上了双保险。第三层架构解耦。SNMP的Manager-Agent模型是紧耦合的Manager需要知道每个Agent的IP和OID。MQTT的发布-订阅模型是松耦合的Publisher不需要知道Subscriber是谁。在设备数量多、上层应用多的场景下MQTT的解耦特性让系统扩展变得容易而SNMP则负责兜底那些不支持MQTT的老设备。注意组合使用不等于两种协议同时跑在同一套数据上。正确的做法是明确分工——SNMP负责设备健康监控MQTT负责业务数据流转两者在数据平台层做关联。2. 协议选型与核心细节拆解2.1 SNMP版本选择v2c还是v3SNMP有三个主要版本v1、v2c、v3。v1基本可以忽略功能太弱。实际项目中主要纠结的是v2c和v3。v2c的优势是配置简单只需要一个Community String团体字相当于密码。缺点是安全性差Community String以明文传输抓包就能看到。v3的优势是支持认证和加密有USM用户安全模型可以配置认证协议MD5/SHA和加密协议DES/AES。缺点是配置复杂不同厂商的实现有差异调试起来麻烦。我的建议是内网环境用v2c跨网段或对安全有要求的用v3。如果非要用v2c至少把Community String改掉默认的public和private并且用ACL限制哪些IP可以访问。配置v3的时候有个坑不同厂商对认证和加密的组合支持不一样。比如某些国产工控设备只支持MD5认证DES加密你配SHAAES就连不上。所以上v3之前先用snmpwalk测试一下目标设备支持哪些组合。# SNMP v2c 测试命令 snmpwalk -v 2c -c your_community 192.168.1.100 .1.3.6.1.2.1.1 # SNMP v3 测试命令认证加密 snmpwalk -v 3 -l authPriv -u your_user -a SHA -A auth_password -x AES -X priv_password 192.168.1.100 .1.3.6.1.2.1.12.2 MQTT Broker选型别一上来就上集群MQTT Broker的选择取决于设备规模和可靠性要求。常见的开源方案有Mosquitto、EMQX、VerneMQ、NanoMQ。Mosquitto是最轻量的单机跑几千个连接没问题适合中小规模项目。它的优点是资源占用低一个树莓派就能跑缺点是集群能力弱官方不支持原生集群。EMQX是国内用得最多的支持集群、规则引擎、数据桥接功能很全。缺点是资源占用相对高Java系的Broker比如ActiveMQ Artemis更重。EMQX用Erlang写的单节点跑几万连接很轻松。NanoMQ是近几年起来的主打边缘计算场景资源占用极低适合跑在网关设备上。我的选型逻辑是这样的场景推荐Broker理由单车间、设备数500Mosquitto够用、简单、好维护多车间、设备数500-5000EMQX单节点功能够、社区活跃跨地域、设备数5000EMQX集群高可用、可扩展边缘网关内置NanoMQ资源占用低提示不要一上来就搭集群。我见过太多项目设备才200台就上了3节点EMQX集群结果运维复杂度翻倍收益为零。先单节点跑到了瓶颈再扩。2.3 数据模型设计OID与Topic的映射SNMP的数据用OID对象标识符表示是一棵树状结构。比如.1.3.6.1.2.1.1.1.0表示系统描述.1.3.6.1.2.1.1.3.0表示系统运行时间。MQTT的数据用Topic表示是分层级的字符串。比如factory/workshop1/press001/temperature表示1号车间1号冲压机的温度。组合使用的关键一步是建立OID和Topic的映射关系。我的做法是在数据平台层维护一张映射表设备类型SNMP OIDMQTT Topic数据含义冲压机.1.3.6.1.4.1.2021.10.1.3.1factory/press/{id}/cpuCPU负载冲压机.1.3.6.1.4.1.2021.10.1.3.2factory/press/{id}/mem内存使用冲压机私有OIDfactory/press/{id}/temp模具温度电表.1.3.6.1.4.1.2021.10.1.3.3factory/meter/{id}/power有功功率这张表是双协议组合的“翻译字典”。SNMP采集到的数据写入时序数据库时用OID查表得到对应的TopicMQTT收到的数据写入时用Topic查表得到对应的OID。这样上层应用无论从哪个通道拿数据看到的都是统一的设备模型。2.4 采集频率的权衡SNMP轮询频率和MQTT推送频率需要分别设计。SNMP轮询太频繁会加重设备和网络负担。我一般把SNMP采集分为三档高频档10-30秒CPU、内存、关键接口状态用于实时告警中频档1-5分钟磁盘使用率、温度、风扇转速用于趋势分析低频档15-60分钟资产信息、配置备份用于台账管理MQTT推送频率由设备侧决定。对于传感器数据通常1-5秒推一次对于告警事件变化时立即推。这里有个坑不要让设备无节制地推。我见过一个项目温度传感器每100毫秒推一次结果Broker的磁盘IO直接打满。正确的做法是在设备侧或网关侧做聚合比如1秒内的多次变化只推最后一次。3. 实操过程与核心环节实现3.1 环境准备从零搭建双协议采集环境先列一下我这次演示用的环境操作系统Ubuntu 22.04 LTSSNMP工具net-snmp 5.9MQTT BrokerEMQX 5.3 开源版MQTT客户端mosquitto_pub/sub 和 Python paho-mqtt数据存储InfluxDB 2.7目标设备一台支持SNMP的Linux服务器模拟工控设备 一个MQTT温度传感器模拟第一步安装SNMP工具sudo apt update sudo apt install -y snmp snmp-mibs-downloader安装完成后编辑/etc/snmp/snmp.conf注释掉mibs :这一行这样snmpwalk就能解析MIB名称而不是只显示数字OID。第二步搭建MQTT BrokerEMQX提供了apt源直接安装curl -s https://assets.emqx.com/scripts/install-emqx-deb.sh | sudo bash sudo apt install -y emqx sudo systemctl start emqx sudo systemctl enable emqx启动后EMQX默认监听1883端口MQTT和18083端口Dashboard。浏览器打开http://your_ip:18083默认账号admin密码public。第一件事就是改密码。第三步安装InfluxDBwget -q https://repos.influxdata.com/influxdata-archive_compat.key echo 393e8779c89ac8d958f81f942f9ad7fb82a25e133faddaf92e15b16e6ac9ce4c influxdata-archive_compat.key | sha256sum -c cat influxdata-archive_compat.key | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/influxdata-archive_compat.gpg /dev/null echo deb [signed-by/etc/apt/trusted.gpg.d/influxdata-archive_compat.gpg] https://repos.influxdata.com/debian stable main | sudo tee /etc/apt/sources.list.d/influxdata.list sudo apt update sudo apt install -y influxdb2 sudo systemctl start influxdb3.2 SNMP采集端实现SNMP采集的核心是轮询调度和OID批量获取。不要一个一个OID去get那样效率太低。用snmpwalk或者snmpbulkget一次性拿一个子树的数据。比如要拿系统组的所有信息snmpbulkget -v 2c -c public -Cn0 -Cr10 192.168.1.100 .1.3.6.1.2.1.1-Cn0表示不重复-Cr10表示一次最多返回10个变量。这个参数需要根据设备性能调整设太大可能超时设太小效率低。用Python写采集脚本的话推荐用pysnmp库from pysnmp.hlapi import * def snmp_get(ip, community, oid): iterator getCmd( SnmpEngine(), CommunityData(community, mpModel1), # mpModel1表示v2c UdpTransportTarget((ip, 161), timeout2, 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()这个函数返回单个OID的值。实际采集时我会把同一设备的多个OID放在一个列表里循环调用然后统一写入InfluxDB。写入InfluxDB的格式是Line Protocolsnmp_metrics,devicepress001,typecpu value45.2 1690000000000000000 snmp_metrics,devicepress001,typemem value62.8 16900000000000000003.3 MQTT订阅端实现MQTT订阅端要处理三件事连接Broker、订阅主题、处理消息。用Python paho-mqtt的示例import paho.mqtt.client as mqtt import json from influxdb_client import InfluxDBClient, Point from influxdb_client.client.write_api import SYNCHRONOUS # InfluxDB配置 influx InfluxDBClient(urlhttp://localhost:8086, tokenyour_token, orgyour_org) write_api influx.write_api(write_optionsSYNCHRONOUS) def on_connect(client, userdata, flags, rc): print(fConnected with result code {rc}) client.subscribe(factory///temperature) client.subscribe(factory///alarm) def on_message(client, userdata, msg): topic msg.topic payload json.loads(msg.payload.decode()) parts topic.split(/) device_id parts[2] metric parts[3] point Point(mqtt_metrics) \ .tag(device, device_id) \ .tag(metric, metric) \ .field(value, float(payload[value])) write_api.write(bucketindustrial, recordpoint) client mqtt.Client() client.on_connect on_connect client.on_message on_message client.connect(localhost, 1883, 60) client.loop_forever()这里用了通配符来订阅多个设备。factory///temperature会匹配factory/press001/sensor1/temperature和factory/press002/sensor2/temperature但不匹配factory/press001/temperature层级不对。注意通配符订阅很方便但不要滥用。#通配符会订阅所有主题如果Broker上有大量无关主题你的客户端会被淹没。我一般只用做单层通配不用#。3.4 数据关联与统一存储SNMP数据和MQTT数据写入InfluxDB后需要在查询层做关联。InfluxDB本身不支持JOIN但可以通过Flux语言做跨measurement查询。比如要查某台设备最近5分钟的CPU负载和温度cpu from(bucket: industrial) | range(start: -5m) | filter(fn: (r) r._measurement snmp_metrics) | filter(fn: (r) r.device press001) | filter(fn: (r) r.type cpu) temp from(bucket: industrial) | range(start: -5m) | filter(fn: (r) r._measurement mqtt_metrics) | filter(fn: (r) r.device press001) | filter(fn: (r) r.metric temperature) join(tables: {cpu: cpu, temp: temp}, on: [_time])这个查询把同一时间点的CPU和温度关联起来。实际做告警规则时可以基于这个关联结果做复合判断比如“CPU超过80%且温度超过70度”才触发告警避免单一指标误报。3.5 边缘网关上的协议转换很多工业现场的设备只支持RS-485/Modbus不支持MQTT也不支持SNMP。这时候需要在边缘网关上做协议转换。我的做法是在网关上跑一个转换程序用Python的pymodbus读Modbus寄存器然后转成MQTT发布出去。同时网关本身开启SNMP Agent让网管平台能监控网关自身的状态。from pymodbus.client import ModbusSerialClient import paho.mqtt.client as mqtt import time modbus ModbusSerialClient(methodrtu, port/dev/ttyUSB0, baudrate9600, timeout1) modbus.connect() mqtt_client mqtt.Client() mqtt_client.connect(localhost, 1883, 60) while True: result modbus.read_holding_registers(address0, count10, slave1) if not result.isError(): temp result.registers[0] / 10.0 pressure result.registers[1] / 100.0 mqtt_client.publish(factory/gateway01/sensor/temperature, temp) mqtt_client.publish(factory/gateway01/sensor/pressure, pressure) time.sleep(1)这个程序每秒钟读一次Modbus寄存器把原始值除以缩放系数后发布到MQTT。缩放系数取决于传感器的量程和寄存器定义需要查设备手册。提示RS-485总线上的设备不要轮询太快。9600波特率下一次Modbus RTU请求响应大约需要20-50毫秒。如果总线上有10个设备轮询一圈至少200-500毫秒。所以1秒的轮询周期是合理的再快就会丢包。4. 常见问题与排查技巧实录4.1 SNMP采集常见问题问题一snmpwalk超时这是最常见的问题。原因通常有三个Community String不对、ACL限制、网络不通。排查步骤先用ping确认网络通再用snmpget单独取一个OID测试。如果snmpget也超时检查Community String和ACL。如果snmpget能通但snmpwalk超时说明设备响应慢需要调大超时时间和重试次数。# 调大超时到5秒重试3次 snmpwalk -v 2c -c public -t 5 -r 3 192.168.1.100 .1.3.6.1.2.1.1问题二OID返回noSuchObject说明这个OID在设备上不存在。不同厂商的私有OID不一样需要查设备手册或者用snmpwalk遍历整个私有子树找到正确的OID。# 遍历厂商私有子树 snmpwalk -v 2c -c public 192.168.1.100 .1.3.6.1.4.1问题三中文乱码SNMP返回的字符串如果是中文可能会乱码。这是因为SNMP协议本身不指定字符编码。解决方法是在采集端做编码转换通常设备用的是GBK或UTF-8。value varBind[1].prettyPrint() try: value value.encode(latin-1).decode(gbk) except: pass4.2 MQTT常见问题问题一连接被拒绝检查Broker是否启动、端口是否监听、用户名密码是否正确。EMQX默认允许匿名连接但生产环境一定要关闭匿名。# 检查1883端口 ss -tlnp | grep 1883 # 测试连接 mosquitto_pub -h localhost -p 1883 -t test -m hello问题二消息丢失MQTT有三种QoS0最多一次、1至少一次、2恰好一次。QoS 0会丢消息QoS 1可能重复QoS 2最可靠但开销最大。工业场景建议普通传感器数据用QoS 0或1告警事件用QoS 1或2。不要全部用QoS 2那样Broker压力太大。问题三主题设计混乱我见过最离谱的主题设计是device/data所有设备都往这个主题发消息里带设备ID。这种设计的问题是无法做细粒度订阅每个订阅者都要收到所有设备的数据再过滤。正确的做法是按层级设计主题{厂区}/{车间}/{设备类型}/{设备ID}/{指标}。这样订阅者可以按需订阅比如只订阅1号车间的所有冲压机温度factory/workshop1/press//temperature。4.3 双协议协同问题问题一时间戳不一致SNMP采集的时间戳是采集服务器的时间MQTT消息的时间戳是设备的时间。如果设备时间不准两个数据源的时间戳就对不上关联分析会出问题。解决方法在数据平台层统一用采集服务器的时间戳忽略设备上报的时间戳。或者在MQTT消息里强制要求设备带NTP同步的时间戳。问题二设备标识不统一SNMP用IP标识设备MQTT用Topic里的设备ID标识设备。如果两者没有映射关系数据就无法关联。解决方法维护一张设备台账表记录每个设备的IP、SNMP Community、MQTT Client ID、设备ID。采集端写入数据时统一用设备ID作为tag。问题三告警风暴SNMP轮询发现设备离线会告警MQTT的LWTLast Will and Testament也会发现设备离线告警。如果两个通道同时告警运维人员会收到重复告警。解决方法在告警平台做去重同一设备同一时间窗口内的同类告警只发一次。或者明确分工——SNMP告警只发给网络团队MQTT告警只发给业务团队。4.4 常见问题速查表问题现象可能原因排查方法解决方案snmpwalk超时Community错误/ACL限制snmpget单OID测试检查配置、调大超时OID返回noSuchObjectOID不存在snmpwalk遍历子树查手册找正确OIDMQTT连接被拒Broker未启动/认证失败检查端口和日志启动Broker、检查密码MQTT消息丢失QoS设置不当抓包分析提高QoS等级数据无法关联设备标识不统一检查台账表统一设备ID告警重复双通道同时告警检查告警平台去重或分工注意排查问题时先用最小化环境复现。比如SNMP问题就单独用snmpwalk测试不要一上来就查采集程序的代码。MQTT问题就单独用mosquitto_pub/sub测试不要一上来就查订阅端逻辑。5. 性能优化与规模扩展5.1 SNMP采集的性能瓶颈SNMP采集的性能瓶颈通常在两个地方设备响应速度和采集服务器并发能力。设备响应速度是硬限制。老旧的工控设备CPU性能弱SNMP Agent处理一个请求可能需要几百毫秒。如果一台设备有100个OID要采集串行采集需要几十秒。这时候可以用snmpbulkget批量获取一次请求拿多个OID能显著减少交互次数。采集服务器的并发能力可以通过多线程或多进程提升。我一般用Python的concurrent.futures.ThreadPoolExecutor开10-20个线程并发采集不同设备。注意不要开太多线程否则网络IO会成为瓶颈。from concurrent.futures import ThreadPoolExecutor devices [...] # 设备列表 with ThreadPoolExecutor(max_workers20) as executor: futures [executor.submit(collect_device, dev) for dev in devices] for future in futures: future.result()5.2 MQTT Broker的规模扩展单节点EMQX能支撑多少连接官方数据是单节点50万连接但实际项目中受限于内存和文件句柄。每个MQTT连接大约占用几KB到几十KB内存50万连接大约需要几GB到十几GB内存。如果设备数超过单节点能力就需要集群。EMQX集群的核心是会话持久化和消息路由。集群中的节点通过Erlang分布式协议通信一个节点上的订阅者能收到另一个节点上发布的消息。集群部署的坑网络延迟。如果集群节点跨机房部署节点间同步消息的延迟会很高导致消息投递延迟。所以集群节点最好在同一机房内跨机房用桥接Bridge而不是集群。5.3 数据存储的优化InfluxDB写入性能很高但查询性能取决于tag的基数。tag基数太高会导致查询变慢。比如用设备ID作为tag如果有10万台设备tag基数就是10万查询时需要扫描大量索引。优化方法把高频查询的维度作为tag低频查询的维度作为field。比如设备ID是高频查询维度作为tag原始消息ID是低频查询维度作为field。另外设置合理的数据保留策略。SNMP的原始数据保留30天就够了聚合后的数据可以保留1年。MQTT的告警数据保留1年普通传感器数据保留90天。// 创建保留策略 influx bucket create --name industrial --retention 30d influx bucket create --name industrial_archive --retention 365d6. 实际项目中的经验总结6.1 先跑通再优化我见过太多项目一开始就追求完美架构结果三个月都没上线。正确的做法是先用最简单的方案跑通——Mosquitto 单节点InfluxDB 一个Python采集脚本能采到数据、能存下来、能查出来就算成功。然后再根据实际瓶颈做优化。6.2 设备台账是基础双协议组合的核心是设备台账。没有台账SNMP的IP和MQTT的设备ID就对不上数据就是孤岛。台账至少包含设备ID、设备名称、设备类型、IP地址、SNMP版本和Community、MQTT Client ID、所属车间、负责人。台账的维护是个持续工作。新设备上线要录入设备迁移要更新设备报废要标记。我一般用CMDB或者简单的Excel维护关键是有人负责。6.3 监控采集系统本身采集系统本身也需要被监控。SNMP采集脚本挂了你不知道MQTT订阅端断连了你也不知道。所以要用一个独立的监控通道来监控采集系统。我的做法是采集脚本定期向一个独立的MQTT主题发心跳监控平台订阅这个主题超过一定时间没收到心跳就告警。同时采集脚本的日志要集中收集用ELK或者Loki方便排查问题。6.4 安全不能省工业现场的安全往往被忽视。SNMP v2c的Community String明文传输MQTT默认允许匿名连接这些都是安全隐患。最低限度的安全措施SNMP用v3或者至少改掉默认Community并加ACLMQTT关闭匿名连接、启用用户名密码、用TLS加密Broker的Dashboard改默认密码、限制访问IP。如果设备不支持v3和TLS至少在网络层做隔离把设备网络和管理网络分开用防火墙限制访问。6.5 文档和交接工业项目的生命周期很长设备可能运行十年以上。采集系统的文档必须完整包括架构图、设备台账、OID映射表、Topic命名规范、部署步骤、常见问题处理。我踩过最大的坑是一个项目上线两年后原负责人离职新来的人看不懂采集脚本里的OID是什么意思因为文档里只写了OID数字没写对应的含义。所以文档里一定要写清楚每个OID对应什么指标、单位是什么、正常范围是多少。这个内容后续还可以这样扩展把SNMP采集和MQTT订阅的数据接入Grafana做可视化用告警规则做复合判断再对接工单系统实现自动派单。但那是另一个话题了先把双协议采集这条链路跑稳后面的都是水到渠成的事。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进