资讯详情

资讯详情

从边缘网关到云端:智慧农业物联网数据链路架构设计与技术选型

先说个实际场景。我接手一个农业基地的数字化改造时客户给的指标很直白三千多个传感器节点、两百多个大棚、每隔十秒上报一次环境数据现场网络是4G和自建LoRa混合晚上电压还不稳定网关时不时掉线。整个系统要用手机端给农户看数据、让管理人员远程控制设备、还要给大屏做可视化展示。技术选型阶段团队把方案从Spring Boot单体扛一切一步步改成了 Spring Cloud EMQX TDengine uni-app 边缘网关固件的组合中间踩了不少坑也踩明白了很多选型的底层逻辑。这篇文章就把这套架构从业务诉求、数据链路、服务设计到落地配置完整复盘一遍给正在做智慧农业或者工业物联网的团队一个可以直接参考的样本。1. 农业物联网系统的真实技术诉求先搞清楚要解决什么问题很多人上来就问用什么框架但智慧农业系统的坑恰恰不在框架而在数据特征、硬件环境和运维半径这三个容易被忽视的地方。它们直接决定了你要不要上微服务、要不要换时序数据库、要不要加边缘网关。1.1 数据特征高频、强时序、低价值密度并存农业环境数据几乎是标准的时序数据温度、湿度、光照、土壤墒情、二氧化碳浓度每10秒到30秒采集一次。按三千节点、每10秒一条计算一天产生约2600万条记录3000 × 86400 ÷ 10 ≈ 2592万条。这个量级用MySQL不是扛不住而是扛得很痛苦——写入单表没问题但按时间范围做分析、做聚合、做清理时索引和锁的代价会逐渐拖垮业务查询。这套系统的数据还有一个特性单条价值低整体价值高。单看某一条温度数据毫无意义但放到时间轴上就能看出大棚温度变化趋势、判断设备是否异常。所以数据库选型的核心不是能不能存而是能不能高效按时间维度写入和聚合。这也是我们把时序存储独立出来而不是继续在关系库里凑合的根本原因。1.2 硬件环境碎片化弱网、断连、私有协议是常态农业生产现场的物理环境比写字楼恶劣得多。我们遇到的几个典型问题大棚内4G信号时强时弱仪表柜电压波动导致网关不定期重启传感器来自不同厂商走的协议五花八门有Modbus RTU、有RS485透明传输、还有只提供私有报文格式的第三方气象站。这意味着数据链路不能假设网络永远在线。边缘网关必须承担协议适配、本地缓存和断线重传的职责而不是简单做一个串口转网络的透传盒子。选型阶段就要把弱网下的数据完整性当成一等公民来设计否则后面补传输机制非常痛苦。1.3 运维半径过大能远程解决的就别跑现场农业基地往往分布在几十公里甚至跨市的范围某个大棚的采集网关死机了运维人员驱车两小时过去可能只是重启一下。所以系统的另一个硬性要求是远程可运维网关要支持远程配置下发、固件OTA升级、阈值动态修改云端要能随时看到每个网关的在线状态和信号质量。这直接影响了我们对技术栈的取舍。MQTT的遗嘱消息Last Will、保留消息Retained Message、QoS分级都是为这种弱网远程运维场景设计的而边缘计算能力则可以把一部分简单的阈值判断放到本地执行即使断网也能触发风机、卷帘等本地保护动作。1.4 把诉求翻译成具体的选型目标业务诉求关键技术要求对应技术栈高并发设备接入与消息分发海量连接、QoS保证、遗嘱离线感知EMQX高频时序数据存储与聚合高吞吐写入、压缩、降采样TDengine设备告警、业务管理、权限控制服务治理、配置中心、接口聚合Spring Cloud农户端、管理端、大屏多端覆盖一套代码多端运行、弱网适配uni-app弱网环境数据不丢本地缓存、断网续传、协议转换边缘网关固件远程运维与策略调整OTA升级、远程配置、动态阈值MQTT下行链路这张表格是整个架构选型的原点后面所有设计都是围绕这几行目标展开的。2. 设备接入与数据链路设计EMQX TDengine 边缘网关的分工这一节是整个系统的核心。设备数据从传感器到数据库要经过传感器 → 边缘网关 → EMQX → 消费服务 → TDengine这样一条完整的链路。每个环节都有自己的职责边界职责切分清楚了系统才不容易出乱子。2.1 为什么是EMQX而不是自研MQTT Broker或轻量级Mosquitto单机场景下Mosquitto完全够用但我们的接入规模是几千设备 每条消息10秒级上报还要考虑多节点扩展、共享订阅和规则引擎所以选择了EMQX这样的分布式MQTT Broker。核心决策点有三个共享订阅多个后端服务实例组成消费组同一条设备消息只被其中一个实例处理。没有共享订阅就得自己实现消息分发和幂等非常容易重复处理。规则引擎与认证扩展可以通过HTTP认证插件对接自己的用户体系设备上线时动态校验不需要维护独立的账号体系。集群能力后续接入量翻倍时可以水平扩展Broker节点而不需要改动设备端固件。Topic结构上我们采用了分层设计让权限控制和数据隔离更容易agri/{siteId}/{deviceType}/{deviceId}/telemetry // 数据上报 agri/{siteId}/{deviceType}/{deviceId}/status // 在线状态、信号强度 agri/{siteId}/{deviceType}/{deviceId}/cmd // 下行控制指令 agri/{siteId}/{deviceType}/{deviceId}/ack // 控制指令回执QoS策略是telemetry用QoS 1因为环境数据丢一条影响不大但没必要为了效率用QoS 0status用QoS 1并开启保留消息这样后端服务随时能查到最新状态cmd用QoS 1下行控制指令要保证送达同时设备端必须做去重。注意设备离线感知依赖遗嘱消息而不是靠主动断开。网关在连接时设置遗嘱topic为statuspayload为offlineretain为true这样异常断电时EMQX能自动广播离线状态而不是等TCP超时。2.2 边缘网关固件不是转发而是先在本地干一遍活边缘网关在整个链路里承担了远超转发的职责。固件基于嵌入式Linux跑核心模块包括串口/RS485采集线程、协议解析层、本地环形缓存、MQTT客户端、规则引擎、OTA升级模块。分工如下协议适配统一将Modbus RTU、私有报文等转换成内部物模型JSON云端只认一种数据格式避免云上做大量兼容解析。采集与上报分离传感器采集周期可以是10秒但网关按30秒或60秒批量上报一批数据减少弱网下的连接开销和消息量。本地缓存断网期间数据先写入掉电保存的环形缓存比如最多保存48小时恢复网络后按时间戳补传。本地阈值联动温度超过阈值时网关直接驱动继电器打开风机不依赖云端响应。云端告警负责记录和通知本地控制保障时效。这里特别提醒一件事补传数据的消费逻辑必须以传感器采集时间业务时间为准不能以服务器接收时间为准。否则断网恢复后一批补传数据会全部落到当前时间窗口导致曲线出现假高峰。2.3 TDengine超级表建模标签归一化组合查询维度TDengine是这套系统里少有的选对了就明显省心的组件。相比开源的时序数据库方案它的优势集中在这几点写入吞吐高、压缩比好我们实测通常能压到十分之一左右、自带连续查询和分区保留策略不用自己写定期删除任务。建表设计上核心是超级表STABLE。我们把所有设备数据统一进一张超级表用标签区分设备维度CREATE STABLE agri_device_data ( ts TIMESTAMP, val FLOAT, quality INT ) TAGS ( site_id BINARY(32), device_type BINARY(16), device_id BINARY(32), gateway_id BINARY(32), unit BINARY(8) );每台物理设备对应一张子表子表名为设备ID标签记录设备所属站点、类型和网关。查询时按时间范围和标签过滤例如查询某站点最近24小时平均温度就是PARTITION BY site_id加INTERVAL(10m)的聚合查询。保留策略我们配置了两层原始数据保留8天8小时后聚合到10分钟粒度保留90天90天后清理。这样既能保证近期数据精细分析也能给中长期趋势留足样本同时控制磁盘成本。2.4 断网续传与消息对账数据链路完整性保证弱网环境下数据不丢是靠网关本地缓存 云端幂等双保险实现的。网关侧每个批次的消息带一个自增的批次号和处理时间戳云端消费服务收到后做两层校验时间窗口校验消息里的采集时间戳不在未来、不早于配置的历史窗口否则丢入异常队列人工检查。幂等去重用gatewayId batchNo作为幂等键存入Redis重复投递的消息直接忽略。对于真正的消息丢失我们做了业务补偿机制TDengine中每个设备记录最新写入时间last_ts定时任务巡检时如果发现某个网关上报时间滞后超过阈值会自动生成补拉指令通知网关重新补传指定时间窗口的数据。这个机制不复杂但非常有效基本解决了弱网下偶发丢包难排查的问题。2.5 三个组件配合的边界什么活不能让它们干选型时容易犯的错是拿着锤子看什么都是钉子。EMQX再强它也只是消息管道不适合做业务逻辑和持久化分析TDengine再快它也不适合存用户、订单、权限这类关系型数据更不适合做复杂事务Spring Cloud适合编排业务但直接拿它接收海量设备数据性能会很难看。链路分工总结成一句话边缘网关负责适配和缓冲EMQX负责可靠分发TDengine负责快速落盘和聚合Spring Cloud负责业务理解和响应。3. Spring Cloud在农业系统里的定位不是凑微服务而是管好设备与业务的中枢在这个系统里Spring Cloud的核心职责不是把服务拆得很细而是解决设备接入稳定性、业务扩展灵活性和多端接口治理这三个具体问题。3.1 服务拆分按领域边界切不为拆分而拆分农业系统的团队规模通常不大服务拆得太细只会增加运维负担。我们最终的划分是六个服务设备接入服务、设备管理服务、告警联动服务、业务中台服务、数据服务、用户权限服务。设备接入服务消费EMQX消息清洗、幂等处理后写入TDengine是整个系统吞吐量最高的服务。设备管理服务负责设备/网关的增删改查、OTA批次管理、物模型管理。告警联动服务消费TDengine聚合结果和设备状态变化触发告警规则、联动策略。业务中台服务地块、农事记录、人员组织、生产计划等农业业务流程。数据服务统一封装时序数据的查询聚合接口给前端和报表使用。用户权限服务认证、授权、租户隔离。拆分标准很简单改动频繁且独立于其他模块的业务拆出来稳定且底层的资源逻辑保留为独立服务跨服务调用能避免就避免。比如设备接入服务不依赖业务中台设备管理服务也不直接调TDengine所有时序查询都走数据服务这样查询逻辑变更时前端无感知。3.2 与EMQX的桥接消费共享订阅与幂等处理Spring Cloud服务消费EMQX消息的标准姿势是使用MQTT客户端库以共享订阅方式订阅$share/group1/agri////telemetry这样多个实例分担消费压力且同一个消息不会被重复处理。消费逻辑的细节值得展开先做协议格式校验JSON Schema再做字段级校验温度范围是否合法、时间戳是否合理通过后写入TDengine。写入失败的消息不允许直接丢弃而是放入内部重试队列连续重试失败再落到本地磁盘或异常消息表由定时任务扫描重新投递。实际操作中建议关闭消费端的自动ACK等数据成功写入TDengine后再手动ACK。否则消息在消费端崩溃时丢失EMQX以为已经处理完了实际数据根本没入库。3.3 容灾与降级外部依赖挂掉时系统不能跟着挂农业现场环境决定了系统经常要面对部分依赖不可用的情况。我们的容灾设计分三层TDengine写入失败消费端将消息暂存到本地磁盘队列同时把Redis中的存储健康标志位置为false。恢复后自动重放并按时间排序写入保证数据时间线不混乱。Redis不可用告警联动服务启动降级模式只保留规则引擎的内存态执行关联查询功能临时禁用避免缓存故障连带告警链路瘫痪。EMQX短暂不可用设备端网关本来就有本地缓存所以Broker重启并不会造成设备数据丢失。消费端要做的就是连接恢复后重新订阅并清掉过期的消费位点。另外下行控制指令必须检查设备最新状态。发送打开阀门指令前先查设备状态缓存如果网关离线直接返回设备不在线而不用发到MQTT避免指令积压在Broker里造成误操作。3.4 Nacos配置中心的下发价值改阈值不用再发版农业系统的控制策略经常要调比如夏季温度阈值、冬季湿度阈值不同品种的作物参数还不一样。我们把这类配置统一收口到Nacos配置中心配置变更通过Spring Cloud Bus通知到各服务实例不需要改代码、不需要重启。比如告警规则中的温度阈值就是一条配置alarm: rules: temperature: max: 38 min: 5 window: 10m现场运营人员在管理端修改阈值Web接口只负责更新Nacos配置监听配置变更的服务自动刷新内存中的规则对象。这套机制上线后运营同事自己就能调整参数研发介入频率大大降低。3.5 边界什么时候其实不需要Spring Cloud一个必须说清楚的问题不是所有智慧农业系统都需要Spring Cloud。如果设备量只有一两百个、业务场景就是看数据、报表单体Spring Boot加EMQX加TDengine完全够用上微服务反而让部署和排障变复杂。我们之所以保留Spring Cloud是看中三个能力服务治理多个团队并行开发互不阻塞、配置中心动态调整策略、网关统一认证、限流。如果你的项目规模没到这个程度别被标配绑架。4. uni-app三端覆盖农户端、管理端、大屏巡检怎么落地终端侧的选型综合考虑了开发效率和覆盖范围。农户要的是简单易用的App和微信小程序管理人员要的是功能完善的管理端领导要看的是可视化大屏。如果用原生开发做三套维护成本难以接受所以我们选了uni-app一套Vue代码同时产出App、小程序和H5。4.1 一套代码三端运行的边界与取舍uni-app的多端编译能力确实好用但不是所有功能都能平滑跨端。我们的做法是按角色拆页面而不是按端拆应用。农户端页面尽量简单清爽只展示与当前农户相关的设备、告警和控制操作管理端页面保留复杂表格、批量操作、地图分布等能力大屏单独做成H5应用用大屏分辨率适配。条件编译是躲不开的工具。比如扫码功能在App端和小程序端API不同我们用条件编译分别调用对应的扫码API文件预览在App端和小程序端的差异也通过条件编译隔离。这样公共代码里不需要到处写if else判断平台。4.2 通信方式HTTP为主、WebSocket做实时、MQTT前端不直连有一种说法是前端直接MQTT连EMQX消息实时性最强实际落地我建议不要这么做。原因很简单前端直连会暴露Broker地址和认证凭证安全隐患大Web端和App端的连接状态管理复杂断线重连逻辑和网关还不能共用一套。我们最终选择的通信方案是HTTP/HTTPS承载90%以上的业务请求RESTful接口通过Spring Cloud Gateway统一鉴权和限流。WebSocket用于告警弹窗、设备状态变化、控制回执的实时推送。后端服务订阅MQTT消息后通过WebSocket广播给前端前端不感知MQTT的存在。数据轮询兜底WebSocket断线时前端自动切换到10秒轮询模式保证数据仍然可见。控制操作的交互细节也花了些心思。农户在App上点击开启卷帘先进入pending状态显示已发送等服务端通过设备状态topic确认网关执行成功后再更新为已开启。这样即使弱网导致回执延迟用户也不至于反复点击。4.3 图表与大屏组件选型先聚合再展示多端图表我们用的是开源图表库的方案App和H5都能渲染ECharts小程序端使用对应适配版本。重点是大屏数据不能直接查原始时序一定要让数据服务先做降采样聚合返回分钟级或小时级数据点否则大屏在多设备、多指标同时刷新时会明显卡顿。具体优化手段包括多指标接口合并为一个批量接口减少HTTP往返前端图表在数据切面做平滑动画避免数据跳变造成视觉闪烁大屏刷新周期设为10秒而不是实时因为数值变化本身是渐进的10秒刷新足够用户感知趋势。4.4 扫码绑定与设备物模型的落地经验设备绑定时我们直接用uni-app的扫码能力读取设备二维码中的序列号然后调用设备管理服务完成绑定。这里有个容易忽略的坑不同厂商设备的序列号格式不统一有的带横杠有的是纯数字有的还含字母大小写。我们在物模型层统一了规范字段统一为sn统一小写后端校验时做标准化处理避免因为格式问题导致绑定失败。5. 从选型到落地部署、配置与踩坑记录架构设计得再好落到部署和运维阶段还是会遇到一堆意想不到的问题。这一节把部署形态、关键配置和生产环境踩过的坑整理出来可以当作实施阶段的检查清单参考。5.1 部署形态从一台服务器到逐步扩容初期设备量不大时单机完全够用一台服务器同时跑EMQX、Nacos、Spring Cloud各服务实例、TDengine和Nginx。不要一开始就上集群先把业务跑通再按监控数据决定扩容。我们的扩容路径是这样的阶段部署形态适用规模单机起步一台物理机/高配ECS设备量 1000拆分部署EMQX独立、TDengine独立、应用服务多实例设备量 1000 - 5000高可用阶段EMQX集群、Nacos集群、TDengine多副本设备量 5000跨多站点单机阶段EMQX的默认配置可以直接用但要注意两点文件描述符上限和最大连接数。Linux系统默认文件描述符限制通常是1024不调大会直接限制并发连接数部署前务必检查。5.2 关键配置示例EMQX认证、TDengine策略、Nacos配置EMQX启用HTTP认证插件让Broker在设备连接时回调业务系统校验凭证可以复用已有的设备管理账号体系# 在 emqx.conf 中配置认证后端 authentication { backend http http { url http://device-service:8080/internal/auth method post body username${username}password${password}clientid${clientid} headers {content-type: application/x-www-form-urlencoded} } }TDengine保留策略通过数据库参数控制避免自己写清理脚本CREATE DATABASE agri_data KEEP 8d WAL_FSYNC_PERIOD 3000;这里的KEEP 8d表示原始数据只保留8天。对于聚合数据我们单独建库保存保留90天。实际生产里要把WAL_FSYNC_PERIOD根据磁盘性能调高一些否则高频写入时I/O压力很大。Nacos配置中心保存各服务的公共配置和应用配置服务启动时统一拉取配置修改后自动刷新。以设备接入服务为例spring: cloud: nacos: server-addr: ${NACOS_ADDR} config: file-extension: yaml group: AGRI_GROUP5.3 踩坑记录我们踩过的五个大坑坑一EMQX保留消息导致磁盘膨胀。状态类topic用了retain之后每次状态更新都会写一条保留消息长时间运行后磁盘占用持续增加。解决方法是控制retain消息数量和topic数量定期清理无用retain消息同时在监控里盯EMQX的存储指标。坑二遗嘱消息payload格式配置错误导致设备误报离线。网关设置遗嘱时payload写成了普通字符串而状态topic的payload是JSON后端解析直接报错。后来统一约定所有状态topic的payload都是JSON并把遗嘱消息的retain标志位设成true。坑三TDengine标签字段长度不够。某个设备ID超长写入子表时直接报错。TDengine对binary字段长度有限制设计标签时varchar/binary要给足余量否则上线后很被动。我们后来统一把device_id缩短为32位以内的业务编码。坑四边缘网关时间漂移导致数据错乱。设备长时间运行后本地时钟会偏移上报数据的采集时间戳出现未来时间甚至多年前时间直接写入TDengine会污染时间线。解决方法是网关内置NTP对时机制云端消费时对超过当前时间15分钟的数据做丢弃并告警。坑五共享订阅消费不均。多个消费实例订阅同一个共享组某些partition消息量大导致处理倾斜。后来我们按siteId做消息路由和负载调整均衡了各实例的处理压力。这些坑单看都是小问题但每一个都可能在半夜值班时跳出来变成事故。早期把这些边缘情况处理清楚后期运维会省下很多跑现场的功夫。根据我个人的落地经验这套技术栈方案适合大多数中等规模的智慧农业和工业物联网项目。核心思路是让每一层干好自己最擅长的事而不是把所有功能都堆到一个大单体里。如果你正在做类似的系统建议先按业务规模评估一下是否真的需要全部组件——如果设备量不大Spring Cloud和EMQX集群可以先省掉但TDengine和边缘网关缓存机制务必保留这两样在真实农田环境里的价值无法替代。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →