见过太多类似的场景:设备端的传感器、PLC、仪表都调试通了,数据也能上报了,结果卡在平台选型上。找现成的云平台,功能确实全,可就是太重了,部署要好几台服务器,定制一个字段要等厂商排期,数据还不好迁出来;完全从零自研,又得折腾协议解析、消息并发、时序存储这些跟业务关系不大、但哪一个都绕不开的底层问题。后来我把自己在几个项目里沉淀下来的轻量级物联网方案整理成了开源项目UNIHH-IOT,前后端代码全部开源,核心目标就一个:让工业数字化转型项目用得起、跑得动、改得动。这篇文章会把这套平台的架构设计、部署流程、设备接入实操和二次开发路径完整地梳理一遍,给正在做方案选型或者准备自建物联网平台的朋友一个能直接参考的路线。
先说清楚一件事,UNIHH-IOT不是什么功能堆砌的大杂烩。它聚焦在工业物联网最核心的四件事上:设备接入、数据存储、告警通知、可视化展示。在这个范围里做到够用、好用、可扩展,而不是急着把MES、ERP、BI全都塞进来。轻量级不是功能少,而是复杂度可控;高性能也不是单纯堆机器,而是用合理的设计把钱花在刀刃上。
1. 先聊清楚:工业物联网平台到底该不该“轻”
我见过太多项目把平台选得又重又豪华,最后项目不是死在设备接入上,而是死在平台本身的复杂度上。重平台在大型集团项目里可能是必要条件,但在大量中小规模场景里,反而是最需要警惕的坑。
1.1 重平台为什么在中小项目里容易翻车
第一个问题是硬件成本和运维成本。一个产线监测项目可能只有几十台设备、几百个点位,数据量远没有想象中那么大,但平台侧动不动就要求几台高配服务器,还要配专门的运维人员。要知道工业项目的利润本来就不算厚,预算大头已经花在传感器、网关和施工上了,留给平台的钱很有限。第二个问题是学习曲线。重平台通常有自己的一套配置体系和术语体系,新来的人要学很久才能上手,项目交付后企业自己没人能维护,后续每次改动都要靠外部团队,时间成本和费用都不可控。第三个问题是定制闭环。大平台改一个字段、加一种存储策略、对接一套内部系统,常常要等厂商排期,一个很简单的需求排到几个版本之后,业务早就等不及了。
1.2 UNIHH-IOT的定位:边缘友好、云端可扩
UNIHH-IOT的定位正好卡在“小项目不浪费、大项目能扩展”这个区间。最低配置2核4G内存就能把后端服务、时序数据库、前端页面全部跑起来,普通工控机或者一台闲置服务器就能撑起一个小型车间的监控需求。等设备数量上来之后,接入服务可以单独拆出来部署,通过负载均衡横向扩展,数据存储层也能从单机平滑演进到集群。这种设计照顾了两类项目:一种是预算有限、希望一次部署长期运行的中小项目;另一种是前期规模不大、但后期明确会扩容的成长型项目。两条路都能走,不用一上来就赌一个庞大架构。
1.3 全开源带来的三个直接优势
“前后端全开源”这个点,实际项目里的价值远不止省授权费。首先是安全性可控,代码都在自己手里,有漏洞可以自己审计修复,不用担心闭源系统的黑盒风险,尤其政企项目对外包代码做安全评估时,全开源能省掉大量沟通成本。其次是集成自由,工业现场往往要对接MES、ERP、协同办公工具,还有一堆内部定制的业务系统,这些对接没人比内部团队更熟悉,有了源码,想加接口就加接口,想改逻辑就改逻辑。第三是成本结构透明,没有按连接数收费的授权费,没有“高级功能”付费墙,预算可以提前算清楚,不会突然冒出额外账单。对很多企业来说,这三点比任何花哨的功能都实在。
2. 核心架构拆解:高性能和轻量靠什么撑起来
轻量不等于简陋。一个平台能跑起来不难,难的是在低配置下还能扛住工业设备高频上报的压力。UNIHH-IOT的架构思路可以概括成四层:设备接入层、消息路由层、数据存储层、业务与展示层。
2.1 模块划分:接入、路由、存储、展示各管一段
接入层负责协议解析,像Modbus、OPC UA、MQTT、泛化的HTTP上报都放在这一层,以插件形式注册。消息路由层处理设备上下线、消息分发和简单的规则前置判断,比如某些点位在进存储之前先做阈值过滤。数据存储层同时使用时序数据库和关系库:点位历史数据进时序库,设备元数据、用户权限、告警记录进关系库。业务与展示层通过统一API取数,不直接操作数据库。这样拆的好处是边界清楚,换数据库、加协议、改前端都不会牵一发动全身。如果哪一层出了问题,排查范围也能迅速缩小。
2.2 设备接入层:协议插件化与线程模型
接入层最忌讳把所有协议解析逻辑写在一个大类里,会导致互相耦合、难以测试、上线容易出问题。UNIHH-IOT把每个协议设计成独立插件,比如modbus-tcp、opc-ua、mqtt、dl645,通过配置动态加载。协议插件和主服务之间定义好统一接口,新增协议只需要在插件层做适配,主服务代码不用动。线程模型上,网络读写使用少量长连接线程,协议解析不阻塞IO线程,避免某台设备交互卡住拖累整个接入服务。这块的代码逻辑大致是这样的:
public interface ProtocolProcessor { // 协议标识,比如 modbus-tcp、opc-ua String protocolName(); // 设备连接建立后完成初始化 void onConnected(DeviceSession session); // 处理平台下发给设备的指令 void onCommand(DeviceSession session, CommandPayload payload); // 处理收到的原始报文,解析后交给消息路由层 void onData(DeviceSession session, byte[] rawData); }插件化之后,项目的接入能力扩展就变成了一个“按插件开发”的常规任务,很多现场遇到的私有协议也能快速接入,不会因为改主流程而引入新的风险。
2.3 数据层选型:时序库加业务库的搭配
工业物联网的数据特征非常明显:写入量大、读取集中在最近一段时间、需要按时间聚合计算。普通关系库在千万级点位数据面前很容易出现写入瓶颈,查询聚合也要花不少功夫。专门为时序数据设计的存储引擎在写入吞吐、分区策略、数据压缩上都做了针对性优化,能省下大量存储空间。UNIHH-IOT默认采用开源时序数据库TDengine存点位历史数据,用关系库存业务数据。点位比较少的项目可以只用关系库,但点位一旦超过一万个,强烈建议老老实实把时序数据独立出来,否则后面做报表、做趋势分析的时候性能差距非常明显。
2.4 前端和后端的通信方式
前端采用Vue技术栈,业务数据走RESTful API,实时点位和告警推送走WebSocket。大屏页面如果点位很多,前端不会每秒钟把全量数据重新拉一遍,而是订阅增量变化,后端把同一时间点变化的点位打包推送过来,这样带宽占用和页面刷新频率都能压下来。用这个思路做几百个点位的大屏,页面流畅度会明显好于轮询方案。前端页面和后端服务之间只走API契约,也方便以后把页面替换成企业自己的定制界面,不改动后端逻辑。
3. 部署一套UNIHH-IOT:从空机器到跑通全流程
部署这块其实没那么神秘,但确实有一些细节容易踩坑。下面按照实际动手的顺序,把从准备到验证的完整流程过一遍。
3.1 最小部署硬件清单
以车间级规模来算,几十台设备、几千个点位,用2核4G内存的虚拟机或工控机就够了。如果点位规模再大一点,建议数据存储服务单独给一台4核8G的机器,磁盘用SSD。这里特别想提醒一句:时序数据写盘非常频繁,机械硬盘在高频写入下延迟会明显升高,别在这个地方省成本。如果现场需要串口采集Modbus RTU设备,还得配一个串口服务器或者USB转485模块,平台侧通过网络访问串口服务器。
| 角色 | 建议配置 | 主要用途 |
|---|---|---|
| 核心服务 | 2核4G以上 | 后端服务、告警引擎、API接口 |
| 数据存储 | 4核8G,SSD | 时序库、关系库 |
| 前端静态资源 | 1核1G | Nginx托管静态页面 |
| 边缘接入网关(可选) | 2核4G | 协议解析、本地缓存、断网续传 |
这个清单不是一个绝对值,而是给“刚刚够用”一个参考。真到项目里,我会先按上表估算,再留出一倍的余量,避免业务增长后马上又得换机器。
3.2 基于Docker Compose的一键拉起
UNIHH-IOT仓库里提供了完整的容器编排文件,本地只要装了Docker和Docker Compose,一条命令就能启动整套服务。
services: unihh-tsdb: image: tdengine/tdengine:3.0 environment: - TAOS_FOO=bar volumes: - tsdb-data:/var/lib/taos unihh-core: image: unihh/iot-core:latest ports: - "8088:8088" - "1883:1883" unihh-web: image: unihh/iot-web:latest ports: - "80:80" volumes: tsdb-data:实际部署时要注意一个常见的坑:很多工业现场是纯内网环境,无法直接访问外部镜像仓库。我的建议是在有网络的环境下先把镜像pull下来,再用docker save打成tar包,拷进生产环境后用docker load导入。这一步能省去大量现场网络问题,尤其是项目时间紧的时候,非常管用。
3.3 配置文件和初始化步骤
核心配置文件主要三个部分:数据库连接信息、接入服务监听端口、前端可访问的API地址。初始化步骤也不复杂,先把时序库和关系库的表结构初始化好,创建默认管理员账号,然后在平台上创建一个产品,定义物模型。物模型的作用会在下一节详细展开,这里先理解成设备的“数据说明书”就行。初始化完,用浏览器访问前端地址,能正常登录就说明后端和数据库之间通信正常。
3.4 验证部署是否健康的三个指标
部署完成后别急着大批量接设备,先用最小代价验证三个指标。第一,服务能不能持续稳定运行,连续观察十几分钟,看有没有进程退出、内存溢出或者连接被重置的报错。第二,日志里有没有周期性噪声,比如数据库连接池不够用、磁盘空间告警、Redis连接失败这类,这些噪声会在设备量上来之后被无限放大。第三,用自带的设备模拟器发一条MQTT数据,确认点位变化能实时出现在前端页面上,告警规则也能正常触发。三步走通后,整个平台的核心链路就验证过了,再开始大规模接入设备,心里才有底。
4. 设备接入实操:Modbus和MQTT两条主流路线
工业协议非常多,但真实项目里碰到最多的还是Modbus和MQTT。Modbus用来从PLC、仪表、电表里采集数据,MQTT用来做设备状态上报和平台下发指令。把这两条路线走通,整个接入逻辑基本就通了。
4.1 通过物模型先定义设备是什么
物模型可以理解成给设备立一份标准的数据说明书,定义设备有哪些属性、哪些可读可写、哪些指令可下发。设备接入之前,先要在平台上建产品,产品下面再挂具体设备。设备唯一标识一般叫DeviceKey,后续所有消息都会带上这个字段。属性定义时要写清楚数据类型和单位,比如温度是float、单位是℃;压力是float、单位是MPa。这样后续告警规则、可视化图表都能自动读取这些定义,不用每个页面再配一遍。
{ "productKey": "air_compressor", "productName": "空压机监测", "properties": [ { "code": "pressure", "name": "出口压力", "dataType": "float", "unit": "MPa" }, { "code": "temperature", "name": "排气温度", "dataType": "float", "unit": "℃" } ] }物模型定义得越规范,后面的开发量越小。有些团队一开始嫌麻烦,不建物模型直接让设备往平台塞数据,短期看着省事,等报表、告警、大屏做起来时,每个地方都要额外处理字段含义和数据单位,反而更痛苦。
4.2 Modbus RTU/TCP接入:轮询策略和寄存器映射
Modbus是典型的主从协议,由平台主动发起请求,设备被动响应。这意味着平台侧必须有轮询策略,而且轮询周期要考虑现场复杂情况。周期太短,串口带宽和设备响应能力跟不上;周期太长,数据实时性又变差。一般现场设备轮询周期在1到10秒范围内,具体要看点位数量和串口波特率。寄存器映射表是Modbus接入的重心,一个点位对应一个寄存器地址、数据类型、缩放系数,地址对不上,数据就全是乱的。接新设备时,第一件事就是对照设备手册把寄存器映射表整理出来。
| 点位 | 寄存器地址 | 数据类型 | 缩放 | 单位 |
|---|---|---|---|---|
| 排气温度 | 40001 | uint16 | 0.1 | ℃ |
| 出口压力 | 40003 | uint16 | 0.01 | MPa |
| 运行状态 | 40005 | uint16 | 1 | 布尔 |
缩放系数是一个特别容易出错的点。很多仪表上报的是整数,比如数值856,实际代表85.6℃,如果忘了乘以0.1,后面所有分析都是错的。这类问题排查起来很隐蔽,一定要在接入阶段就把映射表核对清楚。
4.3 MQTT接入:主题设计、QoS选择和心跳保活
MQTT适合网络环境相对稳定的设备或网关,很多DTU、边缘网关原生支持MQTT上报。主题建议按照设备维度划分,例如devices/{DeviceKey}/telemetry,这样消息的归属清晰,后端订阅也方便。QoS建议用1,保证消息至少到达一次,代价是可能产生重复消息,后端在入库时按消息ID做去重或者幂等处理。QoS0虽然网络开销最小,但可能丢消息,采集关键状态时不太合适。心跳保活是另一个容易忽略的点,比如设备每30秒发一次心跳,平台连续多个周期没收到就判定离线,离线事件同时推送给前端和告警模块,运维人员能第一时间知道哪台设备掉线了。
4.4 在线状态和设备影子的处理
设备离线时,平台需要缓存最后一次上报的数据,这个机制叫设备影子。设备重启之后,前端可以立刻看到设备最后一次上报的状态,不需要等下一次上报才能展示。在运维场景里特别有用,比如晚上停电了,设备在凌晨2点离线,影子数据就能告诉你离线前最后一刻的温湿度是多少,离线了多长时间,什么时候恢复的。UNIHH-IOT把设备影子作为基础能力内置,接入层不用自己重复实现。
5. 数据到底怎么用:规则、告警、可视化
设备接入只是第一步,真正让平台有价值的是数据加工和呈现。如果数据进来之后只是一个一个堆在库里,那平台最多算个数据库外壳。
5.1 数据预处理:清洗、计算、压缩
原始数据直接入库不是不行,但点位多了之后存储会爆炸,查询也会变慢。常规做法是先做清洗,把明显超范围的数据、传感器故障产生的无效值过滤掉;再做计算,把瞬时流量累积成日流量,把三相功率合成总功率;最后做压缩和保留策略,原始数据保留30天,5分钟聚合数据保留1年。这样数据量能降一个量级,最近一个月的明细查询、长周期的趋势报表都能兼顾。数据保留策略在设计时就要想好,不要等磁盘满了再临时删数据,那时候往往已经晚了。
5.2 告警规则的配置逻辑
告警不是越灵敏越好。最典型的例子是温度小幅抖动,如果把告警阈值设置成超过80度立即触发,一个毛刺就会产生几十条告警,值班人员很快就麻木了。UNIHH-IOT支持持续时长判断,比如说连续3个采集周期都超限才算触发,这样能过滤掉大部分瞬时抖动。告警级别分提醒、警告、严重三级,不同级别推送不同渠道。告警产生之后进入未确认状态,值班人员确认后保留记录,等数据恢复正常后告警自动解除。这套流程看起来简单,但在实际现场能大幅减少误报和告警疲劳。
5.3 可视化大屏和报表的实现思路
大屏页面高不高大上是其次,最重要的是信息密度和可读性。点位数量多的场景,列表式监控加状态颜色变化比花哨的3D动效实用得多。图表部分用开源图表库接时序数据,趋势图、柱状图、热力图都能覆盖。报表按天、周、月做聚合,导出成Excel或PDF,方便企业内部流转。如果企业有更强的可视化需求,UNIHH-IOT的API也能把数据开放给其他BI工具,页面层完全可以替换成企业已有的平台。
6. 二次开发路径:开源项目拿到手该怎么改
前后端全开源的项目,直接部署使用没问题,但大多数企业还是希望对界面、协议、接口做一些定制。这一节讲的是拿到代码之后从哪里入手,怎么改才不至于把项目改坏。
6.1 读懂代码的正确顺序
我建议新接手的人按这个顺序来读代码:先看部署和配置文档,知道系统由几个服务组成、端口怎么分配、配置项是什么意思;再看数据模型和表结构,知道数据存在哪里、表之间的关联关系;然后看接入层的协议插件,理解一条数据从设备到数据库的完整链路;最后才看API层和前端页面。很多初学者一上来就扎进某个协议插件里,结果只见树木不见森林。按顺序建立起整体认知之后,再去做具体改动,效率会高很多。
6.2 常见二次开发场景和对应改动点
不同定制需求对应的修改位置差别很大。加一个新协议,改动集中在接入层新增插件;修改设备属性点,直接改物模型定义配置,不需要动代码;对接第三方系统,在API层新增接口或者订阅事件;定制大屏风格,改前端可视化组件;修改告警推送渠道,改告警引擎的通知插件。以下表格可以作为一个快速指引:
| 场景 | 主要改动位置 |
|---|---|
| 增加新协议 | 接入层新增协议插件 |
| 修改设备属性点 | 物模型定义,无需改代码 |
| 对接第三方系统 | API层新增接口或事件订阅 |
| 定制大屏风格 | 前端可视化组件、主题配置 |
| 修改告警推送渠道 | 告警引擎的通知插件 |
这里最重要的一条原则是:能通过配置解决的不要改代码,能通过插件解决的不要动主流程。改动范围越小,升级主版本时越轻松。
6.3 从技术选型走向长期维护的注意事项
开源平台的维护,最大的风险就是停滞和分叉。有些团队从开源项目拉了一个分支,改动越积越多,跟主线版本彻底脱节,后面想合并上游更新都合并不回去。建议每年规划一次版本升级,把改动尽量做成插件和配置外置。前端组件也尽量少改源码,通过主题配置和扩展路由来实现定制。维护一个开源项目就像维护一套生产系统,引入外部代码时要谨慎评估,自己产生的代码也要写好注释和文档,不然半年后连自己都看不懂。
7. 性能表现与卡顿排除经验
最后聊一下性能和排障。UNIHH-IOT在模拟环境里做过一些测试,这里放几个参考数据,再说说实际项目里最容易遇到的卡顿点。
7.1 模拟环境下的性能参考
在配置为8核16G内存的普通服务器上,接入1000台设备、每台每秒上报一条消息,消息从设备到达平台到写入时序数据库,端到端延迟平均在30毫秒以内。当接入量继续往上翻时,瓶颈通常不在CPU,而在数据库写入和日志IO。小规模项目跑到几百上千台设备,单机完全够用。如果单机扛不住,优先考虑把接入服务独立部署并做负载均衡,而不是盲目升级单台机器的配置。需要说明的是,这个数字和协议类型、消息大小、数据库参数都有关系,不能直接换算成所有场景的承诺值,但可以作为一个参考基线。
7.2 几个容易翻车的性能坑
我实际遇到过的坑,按频率排序大概是这几个:第一,采集频率设置过高,点位每秒采一次但实际根本用不到,大量无用数据把磁盘塞满;第二,数据库连接池配置过小,高并发时请求排队,表现为页面偶尔卡死;第三,前端页面高频轮询接口,比如每秒拉一次全量点位,把API服务打满;第四,日志级别长期开着debug,生产环境每秒刷几千行日志,磁盘IO基本被占满。这些坑在测试环境里不容易暴露,设备量上去之后才会爆发,所以设计阶段就要预留好日志规范、连接池参数、点位频率控制这些基础约束。
7.3 性能瓶颈排查方法论
遇到卡顿时,按数据链路分层排查,而不是在页面上一通乱试。设备侧先确认上报频率和数据量;接入层看消息堆积情况和线程池状态;存储层看慢查询、磁盘IO和内存占用;前端看接口耗时和WebSocket消息量。一层一层排除,通常很快就能找到卡点。排查时可以用系统自带的资源监控命令,也可以看平台内部的链路追踪日志。关键是先把“现象”和“链路”对应起来,再动手去改配置,不要看到卡顿就直接把机器的配置翻倍。
我自己在几个项目实施中最大的体会是,轻量级平台的价值不是省那点服务器钱,而是让团队把精力花在设备、业务和运营上,而不是花在伺候平台本身。UNIHH-IOT前后端全部开源,不绑定任何商业服务,任何人拿到代码都能在自己的内网里跑起来、改起来。如果你正在做一个工业数字化项目,不妨先用模拟设备把平台完整跑一遍,再决定值不值得把它用在你的现场环境里。设备接入走不通的场景,把具体协议和现场情况描述出来,我看到了都会认真回复,这类问题聊起来特别有价值。