我做物联网平台这件事,最早是从用开源软件搭一台测试服务器开始的。当时设备量只有几十台,关心的是能不能收到数据、能不能下发指令。等到真正做企业级物联网平台,才发现规模和稳定性才是两种玩法。市面上聊“物联网平台”的文章很多,但多数是罗列组件名称,真正把方案选型、连接管理、报文吞吐、多租户隔离、运维排障讲明白的内容反而少。我把自己从零参与和落地这样一套平台的经验整理出来,尽量不写教科书,多写实际操作中会踩到的场景和取舍。
企业级物联网平台适合谁看?如果你正准备从设备Demo走向规模化接入,或者正在维护一个连接数不少但总在半夜告警的平台,这篇文章可以直接当参考。做设备端、后端、运维的人都能在里面找到对应的决策点,至少能帮你少走几个月的弯路。
1. 企业级物联网平台的核心能力与选型思路
1.1 企业级与个人项目的第一道分水岭
个人项目里用轮询或者WebSocket就能解决的问题,放到了企业级场景下会变得异常棘手。企业级物联网平台本身要面对的不只是数量和规模,还有复杂的可靠性预期。
举个例子:个人Demo里,一台空气监测设备断线了,谁都不会在意。但一家工厂50多台焚烧炉如果同时上报参数,每台每秒一个点位,平台抖动一下,生产车间的值班人员就会不停接到告警,而告警本身又会反向压垮消息通道。到了这种时刻,我们需要的不再是一个简单的EMQX或者VerneMQ,而是一整套能管理设备生命周期、处理乱序上报、容忍网络分区、支持灰度升级的工程系统。
我做这个平台时,第一件事不是写代码,而是和产品、运维一起定义了三个硬指标:
- 至少支持30万台设备同时在线,消息每秒并发不低于10万条。
- 设备连接断线自动重连,对业务无感知,平台侧RPO不超过10秒。
- 要支持多个租户隔离,每个租户能看到自己的设备、数据和告警,不能因为一个租户的异常流量影响其他租户。
这三点定下来之后,技术选型就变得非常具体。不能说用了某消息中间件就万事大吉,整个链路都要围绕这些指标做取舍。
1.2 选型前的量级推演
很多人容易忽略推演环节,直接套用主流框架。我在选型前做了一次粗略的容量估算。假设每台设备每10秒上传一次数据,每次包含5个点位,那么单设备每秒就是0.5条消息。30万台设备是每秒15万条消息。如果再算上设备状态变更、事件上报、网关聚合等额外消息,消息中间件承担的TPS要超过20万。这是一个需要通过分区和横向扩展来支撑的量级,单机无论如何扛不住。
存储侧更考验设计。还是按每10秒5个点位算,一台设备一天产生的点位记录有43200条。30万台不是全部是高频率设备,我们当时假定真正高频运行的占10%,也就是3万高频设备,日增点位记录约13亿条。这就是为什么最后存储层选择时序数据库而不是关系型数据库来保存点位数据。
1.3 方案选型要考虑五年内的演进
在选择各个组件时,团队内部发生过不少争论。比如规则引擎到底要不要自研、设备接入层能不能直接用云厂商托管服务。最终我们没有选全托管的物联网云产品,核心原因是几个需求满足不了:第一,平台要能部署到客户私有机房,数据不出厂区;第二,网络环境复杂,有的客户现场只有4G,设备不能全部依赖公网云服务;第三,平台需要能和客户已有ERP系统做深度数据打通,这不只是标准API能做到的,还需要在边缘或私有化节点上运行自定义任务。
于是整个平台必须采用一种“搞基座 + 留扩展”的架构。基座是设备接入、消息路由、数据存储与设备管理,扩展是规则脚本、数据转发、消息通知,第三方系统可以对接。面向物联网场景的程序员都知道,设备端往往比云端更难升级,因此云端的协议兼容性要做得非常保守,一个版本要管很多年。这直接影响选型,凡是协议和版本锁定太死的组件,我们基本都不优先考虑。
2. 整体架构分层设计
2.1 从设备端到业务端的逻辑分带
平台的架构不在这一篇文章里画图了,我直接用文字描述分层。整个平台从下往上分成四层:设备接入层、事件处理层、存储与数据服务层、业务应用层。很多人会把规则引擎放在接入层下面,实际上是错误的理解,规则应该贴近事件处理层而不是接入层。
接入层只负责协议解析、鉴权、连接维护、心跳保活。所有设备统一走MQTT协议子集,少量不支持MQTT的老设备通过边缘网关做协议转换。接入层不做消息业务处理,避免因为某个规则脚本执行慢卡住其他设备的报文。接入到的消息立即写入Kafka,这样设备和平台之间解耦,平台任何后端的抖动都尽量不影响设备连接。这也是很多企业级平台的常见做法,机制上就是“先接进来,再慢慢消费”。
事件处理层是平台大脑。Kafka中的每条数据会被一个流处理程序消费,做数据清洗、点位解析、阈值判断、设备状态更新和告警生成。这个环节如果吞吐跟不上,就会出现消息积压。我们当时启动了三组消费者,按照设备ID哈希分区消费,单组消费能力不够就扩容消费者实例。
存储层划分为三类:时序数据库存点位历史数据;Redis存设备最新状态和设备影子;关系型数据库存设备档案、固件版本、用户权限、告警记录。应用层则是标准的微服务集合,设备列表、地图展示、告警台、OTA等。
2.2 连接管理里的“设备影子”思路
连接管理是平台容易被忽视却最值得投入的部分。设备并非随时在线,也不应该每次上报都直接操作数据库。我们采用了“设备影子”的思路,即无论设备是否在线,云端都维护一份设备的期望状态和实际状态,业务方只读写影子,而不直接等待设备回复。
用灯控来类比。用户在小程序上点了一下关灯,这个指令通过API到达设备影子,影子把期望状态标记为“关”。此时如果设备不在线,平台不会立刻给用户报错,而是等设备上线后拿到这个最新状态再执行。这一套逻辑很简单,真正难的是处理指令过期、指令去重和乱序。
我们在影子模块里维护了版本号,每次期望状态变更都带自增版本号。设备执行完指令后会把当前状态和最终版本号上报回来。如果设备的网络延迟导致旧指令后到,影子发现版本号小于当前版本,直接丢弃这条指令。没有这层设计,后面设备越多,幽灵指令问题越严重。
2.3 边缘网关不是“可选项”而是“必要项”
不少设备在工厂现场直接接入平台时使用的是Modbus TCP或串口协议,而不是MQTT。让这些旧设备直接改造固件去适配新协议,既危险又昂贵,于是边缘网关的角色就变得很关键。
网关要做的事非常多。从设备侧读取数据,完成本地协议转换,然后按照一定周期把数据转成标准MQTT报文上行;另外,一些现场紧急逻辑需要在本地闭环,比如温度超过阈值就触发声光报警,不能等数据到了云平台再回转指令,因为公网来回延迟可能超过好几秒。
网关本身也要被平台管理。平台要能远程查看网关在线状态、下发网关升级包、调整采集频率。这本质上是一个四级设备管理模型:平台管边缘网关,边缘网关管子设备。层级多了一层,排查难度也会增加。我们在排查问题时常遇到一种情况,设备上报的业务指标看起来正常,其实是网关缓存的数据不是实时数据。因此网关侧的本地缓存必须带时间戳标记,要能区分“采集时间”与“上报时间”,这是我们在踩了不少坑之后总结出的重要经验。
3. 核心模块的技术细节与实操要点
3.1 MQTT接入层的高可用布点
MQTT是物联网平台绕不开的协议。选择MQTT broker时,我们对比过EMQX、VerneMQ,也考虑过基于自研协议栈的方式。最后选择基于EMQX开源版本做二次开发和运维。原因很简单,它对MQTT 3.1.1和5.0协议支持完整,集群的自动发现机制成熟,插件体系也方便对接Kafka认证和鉴权。
高可用布局上,接入层采用多节点集群,前面挂负载均衡器,MQTT客户端用负载均衡地址做连接。这里要特别提醒,直接使用普通四层负载均衡还不够,如果客户端连接断开,负载均衡器需要感知TCP半开连接,及时切断失效会话,否则会有一堆僵尸连接占用broker资源。
设备连接参数方面,我们统一设置了心跳间隔60秒,同时接受服务端强制心跳。设备如果连续两个心跳周期都没消息,平台判定为离线。心跳周期的设置要平衡功耗和实时性,电池类设备如果频繁心跳会导致续航大幅缩短,这类设备的周期可以放宽到300秒甚至更长。接入层要做支持不同心跳策略的多租户配置,不能一个参数套所有设备。
3.2 设备鉴权与一机一密的落地方式
设备安全中,我们不能把用户名和密码做成全局共享,一旦泄露等于对所有设备敞开大门。我采用的是“产品级密钥 + 一机一密”结合的安全方案。每个产品有一个ProductKey和ProductSecret,设备出厂时烧录自己的DeviceName和DeviceSecret,这个DeviceSecret由平台根据设备唯一标识动态派生。
设备连接平台时,平台校验通过后签发出短期的临时凭证,连接成功后设备的所有后续操作都走这个TLS通道。在具体实现上,连接认证逻辑放在自定义Hook中,每台设备第一次连接的时候调用认证服务,根据DeviceName查询该设备的所属产品信息,再验证DeviceSecret。验证通过后,EMQX会为该连接绑定一组ACL,只允许这个设备发布和订阅自己专属的Topic。
Topic权限也必须严格按设备隔离。很多安全事故其实是Topic通配符放得太宽导致的。某台设备被入侵后如果能订阅其他设备的Topic,整个平台就等于裸奔。我们通常会按“productKey/deviceName/xxx”三段式命名Topic,并在ACL层禁止一切跨设备订阅,宁可让后续新增功能麻烦一点,也不能开这种权限口子。
3.3 时序数据存储与点位建模细节
时序数据库选的是开源版TimescaleDB还是TDengine?我们从查询习惯和历史运维成本出发,最终采用的是TDengine来做主要点位存储,因为它在高并发写入和聚合查询上的表现更贴合设备点位的场景。
点位表结构不能简单做成“时间戳、设备ID、值”这种三列结构。真正到生产环境,点位类型差异很大,有整数型、浮点型、布尔型,还有字符串型状态。一种方式是每个点位都建一张宽表,但会造成表数量爆炸。我们最终用了“一张超级表 + 标签”的方式:一张记录表存储所有点位数据,时间戳为主键,设备ID作为标签列,点位名称作为列。这样聚合查询时按设备ID和点位名称过滤,写入时按设备和标签自动建子表,性能很好。
点位存储的精度也要提前想清楚。如果平台要支持告警回溯,就需要存储秒级甚至毫秒级数据。为了控制存储成本,我们会把原始数据保留15天,超过15天后降采样为1分钟均值保留一年,一年以上则只保留日均数据。这个策略看似简单,实际开发时需要在写入链路里做双写,一份原始数据直接落库,另一份经过窗口聚合程序写入降采样表。
3.4 离线指令、OTA与设备升级的故障处理
OTA升级大概是企业级平台里隐藏坑最多的模块。设备固件升级失败,设备变砖需要返厂,是非常麻烦的事情。我们的思路是把OTA拆成四个阶段:固件上传、升级任务下发、设备下载烧录、结果上报与验证。
升级任务不能是简单的“广播一个URL让设备下载”,因为弱网环境下容易中途断掉。平台侧要支持分片下载和断点续传。设备下载过程中会持续上报进度,平台需要在任务超时后判断是否需要重试,同时限制不能大规模并发下发,避免固件服务器带宽被吃满。
在烧录阶段,设备应该先在备用分区写入固件,确认写入完成后修改启动标志位,再重启切换到新版本。如果设备重启后无法连接平台或者版本号未更新,平台要能把升级标记回滚。由于判断设备是否升级成功需要时间,任务状态机要设置一个合理的确认窗口,我们一般设置24小时,超过48小时仍未确认的设备自动标记为失败。
4. 部署交付与安全加固的完整实操路径
4.1 私有化交付时最容易忽略的环境适配问题
平台做出来之后,部署交付成了新的战场。企业客户普遍要求平台部署在自己的服务器上,甚至有的还要求服务器不连外网。离线环境安装Kubernetes集群和中间件时会遇到一堆依赖问题。我们现在会把所有镜像导出成离线包,通过U盘或者内网文件服务器传输到客户现场,再用Helm一键部署。
资源规划上,刚开始我们给客户建议的配置是8台物理机,其中3台作为Kubernetes工作节点运行核心服务,2台用来跑时序数据库加服务节点,1台承载Redis和MySQL主从,剩下2台留作扩展。这套配置放在十万台设备接入量级下基本能撑住。实际交付时会发现,客户现场机器型号、操作系统版本五花八门,我们还需要提前把基础镜像适配到常见操作系统的内核版本。
有一个容易被忽视的问题,就是服务器时钟同步。物联网平台是强依赖时间戳的系统,如果客户机房的时间不同步,设备上报的数据会被错误地标记时间,时序数据查询会一团乱麻。所以我们交付清单的第一条永远是部署NTP服务,并检查所有节点的时钟偏差在100毫秒以内。这个经验来自一次真实事故,某客户现场设备凌晨三点上报的数据全部在平台上显示为凌晨一点,排查了半天才发现是时钟漂移了半小时。
4.2 容量评估、压力测试与灰度发布的节奏
平台上线前必须做压测。我们自己的规则是,压测之前先做容量模型,至少列出以下参数:模拟设备数、每台设备的消息频率、平均消息大小、峰值倍数、消费端处理时长。只有这些数据确定之后,压测脚本才不是拍脑袋。
我在压测MQTT接入时习惯用JMeter配合EMQX自带的压测工具做混合验证。压测时先按1万台设备起步,每10分钟增加一倍,逐步加到目标值的1.2倍。一旦发现broker的CPU或内存接近80%,立即停止加压,记录当前系统表现和瓶颈位置。很多人会觉得压测就是看能不能扛住目标值,实际上更重要的是找瓶颈,比如Kafka的分区数是否合理、消费者是否需要扩容、数据库连接池是否饱和。
灰度发布同样是平台稳定性的一道防线。我们先把一批测试设备连到新版本上,观察一个发布周期;再放5%的生产设备,观察是否出现连接闪断或消息丢失;确认稳定之后才全量发布。任何版本不能因为测试通过就直接全量推生产,一定要经历一个逐步放量的过程,这个习惯能挽救很多个深夜。
4.3 安全加固清单与实操要点
安全方面,最基础也最重要的就是启用TLS加密。很多设备硬件性能弱,不少团队会用“性能不够”来推脱加密,实际上绝大多数时候性能是够的,只是配置不对。我们使用了ECC证书来降低握手开销,而不是传统的RSA长密钥,设备侧单次TLS握手时间从400毫秒降到90毫秒,效果很明显。
数据权限隔离要有审计功能。企业客户对操作记录的要求很高,谁在什么时候禁用了某台设备、谁能批量升级设备,都必须留下痕迹。特别是脚本规则引擎,如果用户可以编写自定义脚本,要限制脚本不允许访问网络,超时时间控制在3秒以内,避免一段有问题的规则拖垮整个流处理任务。
API网关层也要统一做限流和鉴权。很多平台把设备接入和业务API混在一起,这是典型的坏味道。设备流量应该由MQTT broker承担,业务API走单独的HTTP网关,两边熔断、限流互相不干扰。否则某个租户大批量调用接口,连设备的连接也会受影响,这在我们早期架构中出现过,后来隔离架构才彻底解决。
5. 各环节典型故障排查与心得记录
5.1 设备频繁掉线,问题竟然出在NAT会话老化
上线半年后,某客户突然反馈大量设备频繁上下线。从平台看,设备连接断开又迅速重连,间隔时长几乎一样,像是有规律的清扫。我们翻后台日志,定位到设备IP池都是运营商NAT出口IP。
排查到最后,原因是运营商NAT设备会回收长时间空闲的UDP或TCP映射,而设备侧如果长时间不上报数据,连接在运营商NAT层就被静默丢弃。设备感知不到连接失效,平台却迟迟收不到心跳,之后等平台主动判定离线,设备才会尝试重连。整理出的解决方案有两项:接入层缩短心跳周期,不是让平台更快断开设备,而是让设备更频繁地产生流量;设备端启动之后,选择一个业务低峰时间主动发送一次ping消息,保持NAT映射活跃。
这件事给我的经验是:MQTT协议能保证连接状态,但中间任何一层设备都有可能静默中断。平台侧不能只相信TCP连接,而是要相信应用层心跳,并让设备端对重连有快速响应能力。
5.2 Kafka消息积压,增加消费分区也没能解决
平台经历过一次典型的积压问题:某客户的大屏展示系统突然停止更新,监控发现Kafka的消息消费延迟从5秒一路涨到20分钟。排查时先看消费者组,发现消费者进程全部存活,却没有任何消息被消费。再往下看,发现消费者的线程阻塞在某个外部API调用上,我们之前写规则引擎时直接调用了一个第三方天气服务,这个服务在凌晨发生了超时,超时时间设置成了60秒,消费线程全卡在等待结果上,一次都没返回。
解决的直接手段是先关闭这条外部调用逻辑,消费速度立即恢复。根因上,我们给所有消费链路的第三方调用都增加了独立的线程池和超时熔断机制。规则引擎的主处理流不能直接做网络IO,所有外部依赖都必须异步化。从那以后,规则引擎里加的每一条外部调用都默认最多等待2秒。
这说明消息堆积不一定是下游消费能力不足,反而常常是单个消费者逻辑消费太慢。排查时可以看消费者线程是处于RUNNABLE还是WAITING状态,WAITING状态大多是在等外部资源。
5.3 设备影子状态不一致的排查方法论
设备影子的状态在实际运行中会经常出现“线上页面显示在线、实际设备已经离线半小时”的不一致情况。这类问题最折磨人,因为没有明确的错误日志。排查时要把协议栈拆开,区分是设备端掉线后没通知平台,还是平台收到了消息但没更新影子。
我们在影子更新模块加了事件溯源日志,每次状态变化都会记录变更前值、变更后值、触发消息ID。通过比对消息ID和版本号,能快速定位到底哪个环节丢了消息。最终发现某类设备固件会在连接断开重连后发送一个过期的lastWill消息,这个消息把设备状态重新标成了“在线”,但设备实际上还在重启中。解决方式是在设备认证成功后立即重置遗嘱消息状态,设备完整登录流程走完之前不接受任何状态变更请求。
定位这类问题,要记住一个原则:影子状态变更只认最新版本号驱动,不能凭消息到达顺序做判断。分布式系统里消息乱序是常态,任何状态机都必须设计成单调版本递增的格式。
5.4 数据回填、降采样与历史库冷备策略
时序数据到了后期,查询性能会明显下降。有一次客户要求查询三个月前某设备的一条记录,查询耗时从秒级变成了几十秒,后续还会影响其他查询任务。后来排查发现,是由于该设备不断改变点位集合,导致子表结构膨胀,而TDengine在某个版本下多列模型变更触发合并,拖慢了查询。
之后我们为数据回填和补偿增加了严格的任务队列。回填数据不能直接插入生产库,而是先写到临时表,经过校验后再按照时间窗口合并。同时降采样任务每天凌晨运行,并把超过一年的数据转存到冷备对象存储中。这张“热库 + 温库 + 冷库”的三级数据存储策略,能让查询性能稳定在一个合理范围内,也为后续成本控制留了充足空间。
6. 落地产线之后的迭代升级经验
6.1 不要把平台做成“一次性交付物”
企业级平台从上线那一刻起,才是真正开始积累问题的阶段。我们每两周都会看一次设备连接成功率、消息消费延迟、告警恢复时长等指标,并从这些数据反推优化点。比如刚开始设备连接成功率只有98.7%,听着不低,但在30万台设备基数下,每天有几台连接不上,就会被客户感知到。通过优化设备端重连退避算法和平台接入层参数的调优,才把成功率提升到99.99%以上。
平台的运维能力要不断沉淀成文档和自动化工具。我们有一个专门的工程师负责整理排障手册,每次有人解决了一个疑难杂症,就要把排查过程、涉及组件和恢复步骤补充进去。这本手册成了后期运维的重要资产,也大大降低了新人上手成本。
6.2 小技巧:消息TraceID贯穿全链路
最后分享一个实际操作中很有效的技巧:从设备接入到数据落库,再到告警触发,我们要求每个环节都传递同一个TraceID。设备在上报消息时如果没有消息ID,平台接入层会赋一个全局唯一ID,后续所有日志输出都带上这个ID。
没有TraceID的时候,排查一次告警失效问题,要同时翻设备日志、broker日志、消费日志、数据库日志,眼睛都能看花。加了TraceID之后,一条指令从设备上报到平台存储的完整链路,一条命令就能全部串起来。做企业级物联网平台,这种基础设施级别的“可观测性”投入,回报永远比预期大得多。
我做了这么久平台,最大的体会是:一套物联网平台能不能长期运行下去,根本不在于用了多少框架,而在于最开始设计的时候,是否把协议兼容性、数据隔离、可观测性和故障恢复机制放在了最重要的位置。设备越接越多,业务越来越复杂,这种底层设计带来的优势会越来越明显。如果要做企业级平台,千万别只看官方文档的功能列表,先想想在真实网络环境和多租户场景下,这些功能能不能撑得住、排得了错、扩得动容。