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

资讯详情

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

本地部署物联网平台实操:从MQTT接入到Grafana可视化全闭环

本地部署物联网平台实操:从MQTT接入到Grafana可视化全闭环 做物联网这么多年我越来越发现一个现象很多团队一上来就想接公有云物联网平台结果数据天天往外传带宽费、消息费、存储费层层叠加平台一断服设备就全部失联还要担心数据合规问题。后来自己动手在本地部署了一套物联网平台把 MQTT 接入、数据清洗、时序存储、可视化大屏全部收敛到内网一台服务器上设备数据从不上云延迟毫秒级成本可控故障还能自己排查。这篇文章就把我在本地部署物联网平台过程中的选型思路、搭建步骤、踩坑经验和几个真实场景完整拆给你无论你是做工业设备联网、农业大棚监测还是纯属家里搞搞智能硬件都能找到可以直接抄作业的路径。1. 为什么我放弃了上云转向本地部署物联网平台先说个真实的背景。几年前我帮一家做电机监测的客户做方案他们原本用的是某知名公有云物联网平台。设备侧通过 4G 模组往云上发数据每台设备每月流量费加消息费差不多要十几块钱几百台设备就是每月大几千的固定成本。这还不算最要命的真正让人崩溃的是某次云端升级导致设备接入网关断了整整一下午现场设备数据全部积压在本地缓存里等恢复后重传顺序乱掉告警漏掉了好几个。客户气得拍桌子我也被折腾得够呛。从那以后我开始认真研究本地部署方案。所谓本地部署就是把物联网平台的各个核心组件——消息接入层、规则引擎、数据库、可视化面板——全部安装在自己的服务器或者边缘节点上数据链路完全闭环在内网。它解决的不只是成本问题更是一种控制权和责任边界的转移。1.1 本地部署最直接的三点收益第一是数据主权。传感器数据、设备状态、生产参数这些属于核心资产一旦走公有云等于默许第三方接触数据链路。即便用了加密很多行业的合规审计仍然要求数据不出园区。本地部署天然满足这类约束。第二是延迟和可靠性的质变。内网接入 MQTT消息从设备发出到入数据库端到端延迟通常在 20 毫秒以内而公有云方案至少是百毫秒级别。更重要的是断网环境下本地平台仍能继续采集和存储网络恢复后再向外部系统同步摘要即可这种韧性对产线控制和实时报警来说是刚需。第三是长期的成本曲线。本地部署需要一次性投入硬件和前期搭建时间但运行期的边际成本几乎是电费和磁盘损耗。如果设备数量超过一百台运营一年以上的总体成本普遍低于公有云分摊费用。1.2 什么样的场景适合本地部署不是所有物联网项目都适合本地部署我总结了三类典型场景设备数量多、单台数据价值高的工业场景比如 PLC、数控机床、空压机、能耗表有数据合规要求或涉密要求的园区级项目比如实验室、军工配套、能源站网络环境不稳定或依赖公网会带来风险的地方比如偏远基站、移动车辆、地下室仓储。如果你只是做几台原型设备的演示或者需要弹性扩展的海量消费级设备那公有云仍然有它的优势。本地部署不是信仰问题是取舍问题。2. 选型思路本地物联网平台由哪些组件构成本地物联网平台不是一个大而全的软件而是一组相互协作的开源组件。我做选型时习惯把平台拆成四层接入层、处理层、存储层、展示层。每一层都有成熟的开源方案关键是怎么组合出最适合自己的那一套。访问控制和安全认证也很重要通常和接入层绑定。我实际用的组合是EMQX Node-RED TimescaleDB Grafana。下面我把每一层的候选方案和选型理由展开讲。2.1 接入层MQTT Broker 的选择对比设备与平台之间的第一道门就是消息代理绝大多数设备端 SDK 都支持 MQTT 协议。我对比过市面上主流的几款 Broker见下表。Broker资源占用并发能力学习成本适合场景Mosquitto极低万级连接低小规模原型、嵌入式设备直接跑EMQX中等十万到百万级中生产级、需要规则引擎和集群NanoMQ低万到十万级中边缘节点、嵌入式 LinuxVerneMQ中高并发中高需要分布式集群的老手如果你只是在家里跑十几个设备Mosquitto 完全够用配置文件才几十行。但我的大多数项目预测设备规模都会超过千级所以首选 EMQX。EMQX 的好处不只是性能它自带一个轻量级的规则引擎可以在消息接入时直接做数据转发、函数处理和消息重发布能省掉一个独立的流处理中间件。Docker 部署也只拉一个镜像配置起来非常省心。2.2 处理层Node-RED 为什么是本地物联网平台的最佳粘合剂设备消息处理可以用 Node-RED也可以用 Home Assistant偏家居、ThingsBoard偏行业管理、JetLinks偏国产协议等。Node-RED 在我这里的定位是编程节点而不是完整平台。Node-RED 的核心价值是流式编排。你会看到一个个功能节点通过连线把它们串起来就能实现数据解析、格式转换、API 调用、告警推送。它非常适合快速孵化业务逻辑比如把温度超过阈值的数据转成告警事件或者把设备心跳聚合成在线状态。不需要写大量后台代码直接拖拽即可而且部署后修改流程是热生效的不用重启服务这对于现场调试简直是救命的特性。如果你需要更完整的设备生命周期管理设备注册、固件升级、OTA可以考虑引入 ThingsBoard 或者 JetLinks。但作为本地轻量化平台Node-RED 数据库 可视化通常是性价比最高的组合。2.3 存储层时序数据库和关系型数据库怎么分工物联网数据九成以上是带有时间戳的数值型数据用传统 MySQL 存不是不行只是数据量一上来之后查询变慢、存储膨胀。我建议引入时序数据库来处理高频采样数据。TimescaleDB 是一个 PostgreSQL 插件它既保留完整 SQL 能力又实现了时序表自动分区和压缩。如果你团队里有熟悉 SQL 的人上手门槛非常低。另一种选择是 InfluxDB写入性能好但查询语言和 SQL 差异较大。还有更轻量的 SQLite/CSV 就不推荐了只适合玩具级。我的做法是设备原始采样数据进 TimescaleDB 的时序表设备元数据和用户配置可以继续放在同一 PostgreSQL 实例的普通表里。这样一套数据库同时搞定两类数据备份恢复也简单。2.4 展示层Grafana 与自定义 Dashboard展示层我基本只用 Grafana。它支持 TimescaleDB/PostgreSQL、InfluxDB、Prometheus 等数据源面板配置灵活告警功能也算够用。把时序数据配好 SQL 查询就能生成实时曲线、柱状图、仪表盘。对于内网部署来说Grafana 的轻量和对开源生态的兼容让我没有理由换别的方案。如果你想完全不想依赖开源组件自己写 Web 前端调用 API 查询数据也可以但周期会长很多。对于第一版落地我建议直接用 Grafana 把可视化打通后续业务需要再定制界面。3. 实操用 Docker Compose 快速搭建一套完整的本地物联网平台这里我直接给出一套可复现的部署配置。环境假设一台 Ubuntu 22.04 服务器8 核 CPU、16GB 内存、200GB 磁盘。如果是树莓派这类小机器可以把 EMQX 换成 MosquittoNode-RED 可以继续保留。3.1 安装 Docker 与 Docker Compose 插件服务器上如果没有 Docker先执行以下命令。国内网络环境选择镜像源可以加快速度但本文示例使用默认源演示。# 安装 Docker 依赖 sudo apt update sudo apt install -y ca-certificates curl gnupg lsb-release # 添加 Docker 官方 GPG 密钥和仓库 sudo mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg echo deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable | sudo tee /etc/apt/sources.list.d/docker.list /dev/null sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin # 设置 Docker 开机自启避免重启后平台失联 sudo systemctl enable docker sudo systemctl start docker安装完成后验证一下sudo docker version sudo docker compose version提示Docker Compose 现在通常作为 plugin 随 Docker 一起安装执行命令时是docker compose没有横杠老旧的docker-compose命令可能需要单独安装不建议再用。3.2 编写 docker-compose.yml 编排文件我在本地目录/opt/iot-platform下创建一个docker-compose.yml文件内容如下version: 3.8 networks: iot-net: driver: bridge volumes: emqx-data: emqx-log: nodered-data: timescale-data: grafana-data: services: emqx: image: emqx/emqx:5.7.1 container_name: emqx restart: always ports: - 1883:1883 # MQTT 协议端口 - 8083:8083 # WebSocket 端口 - 8084:8084 # MQTT over TLS 端口 - 18083:18083 # EMQX Dashboard 管理界面 environment: - EMQX_DASHBOARD__DEFAULT_PASSWORDadmin networks: - iot-net volumes: - emqx-data:/opt/emqx/data - emqx-log:/opt/emqx/log node-red: image: nodered/node-red:4.0.2 container_name: node-red restart: always ports: - 1880:1880 environment: - TZAsia/Shanghai networks: - iot-net volumes: - nodered-data:/data timescaledb: image: timescale/timescaledb:2.15.2-pg15 container_name: timescaledb restart: always ports: - 5432:5432 environment: - POSTGRES_DBiot - POSTGRES_USERiot_user - POSTGRES_PASSWORDiot_pass networks: - iot-net volumes: - timescale-data:/var/lib/postgresql/data grafana: image: grafana/grafana:11.1.0 container_name: grafana restart: always ports: - 3000:3000 environment: - GF_SECURITY_ADMIN_USERadmin - GF_SECURITY_ADMIN_PASSWORDadmin networks: - iot-net volumes: - grafana-data:/var/lib/grafana这个配置把四个组网服务放在了同一个iot-net网桥里服务之间可以通过容器名直接互相访问。对外只需要暴露 1883、1880、3000、18083 等端口。数据都挂到了 Docker volume容器删了数据还在。启动前先检查 1883 和 3000 等端口是否被占用用sudo lsof -i :1883看一下。随后执行cd /opt/iot-platform sudo docker compose up -d看到四个容器都变成Up状态后验证服务是否正常。在浏览器访问http://服务器IP:18083可以打开 EMQX Dashboard 登录页默认用户名admin密码就是环境变量里设置的admin访问http://服务器IP:1880进入 Node-RED 编辑器访问http://服务器IP:3000进入 Grafana 登录页。3.3 初始化 TimescaleDB 扩展进入 TimescaleDB 容器创建扩展sudo docker exec -it timescaledb psql -U iot_user -d iot在 psql 中执行CREATE EXTENSION IF NOT EXISTS timescaledb;随后创建一张用于存储设备遥测数据的时序表。这里我设计一个通用的样例CREATE TABLE sensor_data ( time TIMESTAMPTZ NOT NULL, device_id TEXT NOT NULL, msg_type TEXT NOT NULL, value DOUBLE PRECISION ); SELECT create_hypertable(sensor_data, time);create_hypertable是 TimescaleDB 的核心功能它会把sensor_data按时间自动分区。以后几个月的数据不用手动拆表查询依然很快。注意PostgreSQL 默认的iot_user可能没有创建扩展的权限。如果报错权限不足可以在创建用户时指定超级用户或者在初始化时加POSTGRES_INITDB_ARGS。我这里简单起见直接用默认用户。4. 打通链路从设备模拟数据到 Grafana 可视化平台搭好之后最关键的环节就是让数据真正流动起来。我以一台模拟温湿度传感器为例演示从设备发送 MQTT 消息开始到 Node-RED 解析存储最后在 Grafana 展示曲线和告警的完整链路。4.1 用 Python 模拟设备上报 MQTT 数据设备端我用一个简单的 Python 脚本模拟。它每隔 5 秒向 EMQX 发布一条 JSON 消息主题使用devices/device-001/telemetry。先安装 paho-mqtt 客户端库pip install paho-mqtt创建device_simulator.pyimport json import time import random from paho.mqtt import client as mqtt BROKER 192.168.31.100 # 改成你服务器 IP PORT 1883 TOPIC devices/device-001/telemetry CLIENT_ID device-001 def on_connect(client, userdata, flags, reason_code, properties): if reason_code 0: print(连接成功) else: print(f连接失败: {reason_code}) def main(): client mqtt.Client( client_idCLIENT_ID, callback_api_versionmqtt.CallbackAPIVersion.VERSION2, ) client.on_connect on_connect client.username_pw_set(device_user, device_pass) # 如果配置了认证 client.connect(BROKER, PORT, keepalive60) client.loop_start() while True: payload { device_id: CLIENT_ID, ts: int(time.time() * 1000), temperature: round(random.uniform(15.0, 35.0), 2), humidity: round(random.uniform(40.0, 80.0), 2), } result client.publish(TOPIC, json.dumps(payload), qos1) if result.rc mqtt.MQTT_ERR_SUCCESS: print(f已发送: {payload}) time.sleep(5) if __name__ __main__: main()MQTT 协议本身很轻量甚至不需要设备端做任何重试逻辑Broker 会处理 QoS 1 的确认和重发。这也是为什么工业现场那么多设备都喜欢用 MQTT 而不是自己的私有 TCP 协议。4.2 Node-RED 中编写数据流打开 Node-RED 编辑器我们要搭建一个从订阅 MQTT 到写入 TimescaleDB 的流程。节点依次为mqtt in节点服务器填emqx端口 1883主题devices/#QoS 1。因为 Node-RED 容器和 EMQX 容器在同一个 Docker 网络里所以 Broker 地址可以直接用服务名emqx不需要写局域网 IP。json节点将收到的字符串转换成 JSON 对象。function节点做字段校验和格式整理。例如把msg.payload中的ts、device_id、temperature、humidity提取出来拼装成 SQL 插入语句所需的参数。在function节点里可以写简单的 JavaScriptconst msgData msg.payload; if (!msgData.device_id || msgData.temperature undefined) { return null; // 丢弃缺字段的数据 } const point { time: new Date(msgData.ts || Date.now()).toISOString(), device_id: msgData.device_id, msg_type: telemetry, value: msgData.temperature, }; msg.payload point; msg.topic INSERT INTO sensor_data(time, device_id, msg_type, value) VALUES ($1, $2, $3, $4); return msg;postgresql节点数据库配置选择 TimescaleDB 连接输入预编译 SQL 与参数执行写入。这样一个 flow 就完成了从 MQTT 到数据库的落盘。之后如果想加湿度字段可以再建一张表或者扩展 JSONB 字段。对于快速上线我倾向于在时序表里增加一个payload jsonb字段来存完整的扩展属性避免频繁改表结构。4.3 在 Grafana 中制作实时仪表盘Grafana 登录后先添加数据源选PostgreSQL连接地址填timescaledb数据库iot用户iot_user密码iot_pass。然后点击新建 Dashboard添加 Panel用如下 SQL 查询温度数据SELECT time, value AS temperature FROM sensor_data WHERE device_id device-001 AND msg_type telemetry ORDER BY time DESC LIMIT 100;在 Panel 右侧设置时间范围、自动刷新为 5 秒就可以看到温度曲线实时跳动了。如果要做湿度可以加一条 query用msg_type humidity或者从 JSONB 里取值。Grafana 还支持设置告警规则。比如温度持续超过 30 度持续一分钟就通过 webhook 推送到企业微信或钉钉群。这个配置在 Alerting 模块下完成我后面会单独讲。5. 踩坑实录本地部署最常见的五个问题部署过程并不是一帆风顺的这里我挑几个对稳定性影响最大的坑按我的排查顺序列出来。5.1 端口冲突与服务启动失败最常见的问题是部署新服务时端口被占用。比如有些服务器上已经装了 MySQL 占用 3306或者 Nginx 占用 80但很少有人会预占 1883 和 3000。不过 1880 端口有时候会被其它 Web 服务占用。排查方法启动容器前先ss -lntp检查端口状态或者docker compose up -d后看容器日志。容器启动失败通常一查日志就知道是端口绑定失败还是配置错误。遇到占用要么改容器的映射端口为宿主机空闲端口要么停掉占用进程。5.2 Docker 网络模式选择错误导致服务之间不通很多时候 Node-RED 连不上 EMQX是因为 Node-RED 容器不在同一个 Docker 网络里。如果分别用docker run启动容器而没有指定同一个 network它们之间无法通过容器名互相访问。解决办法是坚持用docker-compose.yml管理所有平台服务通过服务名互相访问而不是写死 IP。如果用了宿主机 IP 也能通但一旦容器重建或 Docker 网络重置IP 可能变化。服务名是 Docker 自带的 DNS 解析更加可靠。5.3 EMQX 数据目录权限不足导致容器频繁重启EMQX 5.x 在 Docker 里如果挂载卷的宿主机目录权限设置不对容器启动时会出现文件权限错误。表现为容器启动几秒后退出日志里有Permission denied。解决办法是给宿主机目录设置 1000 权限EMQX 容器内默认用户 UID 是 1000mkdir -p /opt/emqx_data chown -R 1000:1000 /opt/emqx_data然后重新创建容器。这个问题在国产化部署时更容易遇到因为服务器上可能已经跑着其它应用权限策略比较严。5.4 MQTT 客户端连接被拒却找不出原因设备上报数据显示离线EMQX Dashboard 里也看不到连接。常见原因有三个第一设备端没有配置正确用户名密码而 EMQX 默认开启了匿名访问但如果之前设置了认证匿名就被关闭第二设备端订阅主题和发布主题的权限受 ACL 限制第三设备的 ClientID 与另一个设备重复导致后登录的客户端把先登录的踢下线。我建议在调试前先在服务器上用命令测试sudo docker exec -it emqx emqx ctl clients list如果没有客户端连接再检查设备侧网络是否能到 1883 端口telnet 192.168.31.100 1883网络通但连不上那就重点看认证配置。测试时可以把设备侧username_pw_set空掉看是否匿名能连能连就说明认证配置问题。5.5 时序数据库磁盘占用增长过快TimescaleDB 的默认压缩策略并没有开启。采样频率高的设备数据量会快速膨胀几周不处理磁盘就满了。解决办法是开启压缩和保留策略。例如保留 90 天数据并开启压缩ALTER TABLE sensor_data SET ( timescaledb.compress, timescaledb.compress_segmentby device_id ); SELECT add_compression_policy(sensor_data, INTERVAL 7 days); SELECT add_retention_policy(sensor_data, INTERVAL 90 days);压缩策略每 7 天压缩一次超过 7 天的分区保留策略删除 90 天前的数据。这样数据量和查询性能就能长期维持在稳定水平。这步很多人会漏掉导致后期磁盘告警。6. 安全加固与日常运维本地部署不等于局域网裸奔本地平台最常见的误区是反正别人访问不到就不用加安全措施。实际上内网同样存在风险比如被扫描端口、其它业务系统被攻破后横向移动、员工误操作导致数据被修改等。我的建议是至少做以下四件事。6.1 给 MQTT 加认证和鉴权EMQX 默认开启匿名连接所有能访问 1883 端口的人都可以收发消息。生产环境必须关闭匿名访问并为每类设备分配独立的用户名密码。在 EMQX Dashboard 的 Access Control 里可以添加用户。也可以直接在配置文件里开启allow_anonymous false然后在认证模块增加内置数据库或 HTTP 认证。更细的权限控制就是 ACL比如只允许某个用户发布devices/device-001/#订阅devices/device-001/cmd。我实际部署时会把设备分为 telemetry 用户和 command 用户两组telemetry 用户只能发布devices//telemetrycommand 用户只能订阅devices//cmd这样即使某台设备被入侵也无法篡改其他设备的指令通道。6.2 开启 TLS 加密防止内网嗅探如果设备端的处理器性能允许建议开启 MQTT over TLS。EMQX 5.x 支持直接配置证书文件。如果没有 CA 证书也可以自建内网 CA设备端预埋根证书。证书生成和配置过程稍长但对有合规要求的行业是必须项。比如医疗、军工、能源客户看到你裸奔审计直接不通过。如果暂时不想走 TLS也可以把设备接入网段与办公网段隔离从网络层面降低被嗅探风险。6.3 数据备份策略平台里的核心资产是时序数据库和 Node-RED 的 flow 配置。数据库备份用 PostgreSQL 的pg_dump命令即可。Node-RED 的 flow 存在/data/flows.json直接拷贝出来。我写了一个简单的 cron 脚本每天凌晨 2 点备份sudo docker exec -t timescaledb pg_dump -U iot_user -d iot /backup/iot_$(date %Y%m%d).sql cp /var/lib/docker/volumes/nodered-data/_data/flows.json /backup/nodered_flows_$(date %Y%m%d).json find /backup -type f -mtime 30 -delete恢复的时候直接pg_dump导回即可。这里不得不提醒备份不是目的能按时恢复才是目的定期做个恢复演练别等到磁盘坏了才后悔。6.4 资源监控与告警平台跑起来之后别等出事了才看。我用 Prometheus node_exporter 监控服务器 CPU、内存、磁盘再用 Grafana 接上 Prometheus 数据源配置磁盘使用率超过 80% 时告警。EMQX 本身也提供 Prometheus 指标接口可以一并采集。如果不想引太重的监控栈也可以用watch df -h和docker stats --no-stream先顶着但至少要把磁盘和容器状态盯住。物联网平台通常 7x24 小时运行装完跑几天就忘的这种心态我见得太多最后都是磁盘写满或者容器挂掉才发现。7. 几个本地部署的真实落地案例以及我后续的扩展建议平台搭好不是终点能稳定跑业务才是目标。我这里分享两个最近做的项目参考。第一个是工厂空压机组监测。我们给每台空压机装了一个边缘采集器通过 Modbus 读取排气压力、油温、运行状态再转换后走 MQTT 上报到本地平台。Node-RED 负责把 Modbus 寄存器值换算成工程单位写入 TimescaleDB。车间大屏用 Grafana 展示每台设备的实时负荷和 24 小时趋势一旦油温超过 85 度就通过微信机器人告警。整个平台跑在一台四核工控机上已经稳定运行了八个月。第二个是海产品养殖基地的环境监测。水下传感器采集溶解氧、pH 值和温度数据经 4G 路由器汇聚到园区机房的服务器本地平台完成数据清洗和容错OceanBase 这种重量级系统完全没必要。关键的是园区断网时设备数据不会丢网络恢复后自动续传。养殖户最怕的事就是电脑卡住、数据断了导致缺氧没人发现本地部署天然规避了这类风险。做了这么多项目我个人的体会是本地部署物联网平台最核心的收益并不是省钱而是让你真正掌控数据链路的每一个环节。你可以随时打开 EMQX 看设备连接状态直接在 Node-RED 里改逻辑还能在 Grafana 里加一个自定义面板。这种掌控感是在黑盒云平台上体会不到的。最后再分享一个小技巧把整个平台目录同步到 Git 仓库里docker-compose.yml和 Node-RED 的 flows 导出文件都放进去。这样即使服务器重装拉代码下来一条命令就能恢复服务这比任何备份方案都靠谱。如果你的项目还在起步阶段不妨先从这套组合搭一套最小的验证环境然后逐步加设备、加逻辑、加告警你会发现把物联网平台握在自己手里这件事没有想象中那么难。
返回列表