引言
把工厂里所有传感器原始数据不加处理地推到云端,是物联网项目最容易踩的第一个成本坑。一条产线 2000 个测点,按 100ms 采样,每秒就是 2 万个数据点;如果每个点封装成 200 字节的 JSON 走 4G 上行,一天就是 345GB,流量费和云端写入成本会迅速超过项目预算。而实际上业务只关心「温度超过 85℃ 持续 10 秒」这类结论,原始点位中 99% 是可以被聚合、压缩、丢弃的。
第二类问题是可用性。车间网络抖动、运营商基站切换、云端发布窗口,都会让上行链路中断几分钟到几小时。如果控制器依赖云端下发指令才能动作,断网就等于停产。安全上也不允许:一条冲压线的急停逻辑走公网往返,任何一次 RTT 抖动都可能变成人身事故。因此本地闭环控制必须留在边缘,云端只做策略下发与全局优化。
第三类问题是数据合规与时效。部分行业的产线视频、工艺参数不允许出厂区;视觉质检的推理延迟要求低于 100ms,走公网根本达不到。这些都指向同一个结论:在设备与云之间必须有一层具备计算、存储与协议转换能力的边缘节点。
本文按「为什么 → 分层 → 网关职责 → 南向接入 → 北向转换 → 缓存续传 → 规则与推理 → 硬件与软件栈 → 云边协同」的顺序展开,重点讲工程参数与选型依据,而不是概念介绍。协议细节只在必要处引用。
目录
- 为什么需要边缘层
- 边缘的四层模型
- 网关的五大职责
- 南向接入:Modbus RTU/TCP
- 南向接入:OPC UA 的会话与订阅
- 北向协议转换与 MQTT 桥接
- 本地缓存与断网续传
- 边缘规则与流处理
- 边缘本地推理
- 硬件选型与工业防护
- 软件栈、容器化与资源限制
- 边缘与云协同
- 边缘节点可观测性
- 权衡取舍
- 常见坑清单
- 小结
1. 为什么需要边缘层
把边缘层的价值拆成五条可量化的理由,比笼统说「降低延迟」更有说服力:
- 带宽成本:边缘做 1 分钟均值聚合后,数据量可降到原始的 1/600。以 2 万点/秒为例,压缩后上行约 500KB/天,4G 流量成本从每月数千元降到几元。
- 延迟:本地 PLC 与网关在同一交换机内,闭环控制周期可做到 1
10ms;走公网往返至少 3080ms,且抖动不可控。 - 断网可用性:边缘缓存 + 本地规则保证链路中断 24 小时内业务不中断,恢复后补传。
- 数据合规:原始视频、工艺配方不出厂区,只上行脱敏后的指标与告警。
- 算力卸载:视觉质检、振动频谱分析、异常检测在本地推理,单条产线每年可省下可观的云端 GPU 费用。
反过来,边缘层也有代价:设备数量从几百台变成几千台,运维复杂度上升;边缘节点的固件、容器、模型都要有版本管理与灰度通道。判断是否上边缘,可以问三个问题:断网 1 小时业务会不会停?上行流量费是否超过边缘硬件成本?是否有低于 100ms 的闭环需求?任意一个为「是」,就该引入边缘层。
2. 边缘的四层模型
行业里对边缘的划分并不统一,工程上比较实用的是按「离设备多远」分四层:
| 层级 | 典型位置 | 算力 | 延迟 | 举例 |
|---|---|---|---|---|
| device edge | 传感器/执行器/PLC 内 | MCU 级 | 微秒~毫秒 | ESP32、STM32 上跑阈值判断 |
| near edge | 车间机柜、网关 | 4~64 核 ARM | 毫秒~十毫秒 | 树莓派、RK3588、工控机 |
| far edge | 厂区机房、园区 MEC | x86 + GPU | 十毫秒~百毫秒 | 边缘服务器、运营商 MEC |
| cloud | 公有云/私有云 | 弹性集群 | 百毫秒以上 | 训练、全局调度、长周期存储 |
分层的意义在于职责分配:device edge 只做「保命」逻辑(急停、联锁),near edge 做协议汇聚与规则,far edge 做多车间协同与模型训练,cloud 做全局。很多项目失败是把 near edge 该做的事塞进了 MCU,或者把 device edge 的安全逻辑放到了网关。
3. 网关的五大职责
一个合格的工业网关,职责可以归为五类:
- 南向协议接入:向下对接 Modbus RTU/TCP、OPC UA、BACnet、CAN/J1939、Zigbee、BLE、M-Bus、DL/T 645 等,把异构设备抽象成统一的点位模型。
- 北向协议转换:向上把点位模型映射为 MQTT 主题、HTTP 接口或 Kafka 消息,附带设备元数据与时间戳。
- 本地缓存与断网续传:链路中断时写本地磁盘队列,恢复后按序重放,并保证至少一次投递加去重。
- 边缘规则与流处理:在本地做窗口聚合、阈值判断、状态机联动,减少上行量与响应延迟。
- 本地推理:运行 ONNX Runtime、TFLite 或 OpenVINO 模型,完成视觉质检、异常检测等推理任务。
这五条不是并列的选配项,而是能力递进:只做 1+2 的是「协议转换器」,加上 3 才叫可靠网关,加上 4+5 才是边缘计算节点。选型时先明确自己需要到哪一级。
| 职责 | 缺失后的典型故障 | 对应指标 |
|---|---|---|
| 南向接入 | 点位读不到、数据类型错乱 | 采集成功率、轮询周期 |
| 北向转换 | 数据格式与云端不符、元数据丢失 | 上行成功率、序列化耗时 |
| 缓存续传 | 断网即丢数,无法对账 | 队列深度、补传条数 |
| 规则流处理 | 上行量爆炸、响应慢 | 规则触发数、聚合压缩比 |
| 本地推理 | 延迟超标、带宽成本高 | 推理 P99、模型加载耗时 |
4. 南向接入:Modbus RTU/TCP
Modbus 是最普遍的南向协议,理解它的报文结构是排查现场问题的前提。它的 PDU 与 ADU 分层如下:RTU 的 ADU = 从站地址(1B) + PDU + CRC16(2B),TCP 的 ADU = MBAP 头(7B) + PDU。
读保持寄存器(功能码 0x03)的请求 PDU 结构:
请求 PDU: 0x03 | 起始地址 Hi | 起始地址 Lo | 寄存器数量 Hi | 寄存器数量 Lo
读 40001~40002(0-based 地址 0,数量 2): 03 00 00 00 02
响应 PDU: 0x03 | 字节数 | 数据 Hi | 数据 Lo | ...
响应示例: 03 04 00 0A 01 2C 表示两个寄存器 = 10 与 300
异常响应: 0x83 | 异常码 异常码 02=非法地址, 03=非法数量, 04=从站故障
现场最常见的坑是寄存器地址的 1-based 与 0-based 混淆:手册写 40001,实际报文中要填 0x0000。另外功能码 0x01/0x02 读线圈与离散输入、0x04 读输入寄存器、0x05/0x06 写单个、0x0F/0x10 写多个,网关的点位配置必须区分「读」与「写」以及数据类型(U16、S16、U32 大端/小端字序、Float32)。
轮询策略同样重要:Modbus 是主从轮询,一个串口挂 8 台从站、每台 20 个寄存器,若单次轮询 100ms,整轮就是 800ms 以上。优化手段是把相邻寄存器合并成一次读、提高波特率到 115200、按重要性分级轮询(关键点位 1s、统计点位 30s)。串口层要设置合理的响应超时(典型 3001000ms)与重试次数(23 次),并对连续失败做从站隔离,避免一台坏设备拖垮整条总线。
用 pymodbus 做一次带重试与合并读取的采集:
from pymodbus.client import ModbusTcpClient
from pymodbus.exceptions import ModbusException
client = ModbusTcpClient("192.168.1.10", port=502, timeout=1.0)
def read_block(slave, addr, count, retries=3):
for attempt in range(retries):
try:
rr = client.read_holding_registers(addr, count=count, slave=slave)
if not rr.isError():
return rr.registers
except ModbusException as exc:
last = exc
client.close()
client.connect()
raise RuntimeError("read failed after retries: %s" % last)
regs = read_block(1, 0, 10)
temp = regs[0] / 10.0 # 定点数按 0.1 缩放还原为摄氏度
注意 slave 参数在不同版本里叫法有变化(2.x 是 unit,3.x 改为 slave),升级库时要同步改配置;另外读失败后主动 close/connect 一次,能显著降低串口转 TCP 网关的粘包概率。
5. 南向接入:OPC UA 的会话与订阅
OPC UA 面向的是 PLC、DCS、SCADA 这类结构化数据源,比 Modbus 复杂得多:有信息模型、地址空间、安全通道与会话。工程上关注两个参数:采样间隔(SamplingInterval)与发布间隔(PublishingInterval)。
采样间隔决定服务端多久采一次值(如 100ms),发布间隔决定服务端多久把变化打包发给客户端(如 1000ms)。两者关系是:采样快、发布慢,适合高频变化的模拟量;采样慢、发布快,浪费带宽。典型配置是采样 100~500ms、发布 1000ms、队列长度 10、DiscardOldest=true。
MonitoredItem: NodeId=ns=2;s=Line1.Temp SamplingInterval=100ms QueueSize=10
Subscription: PublishingInterval=1000ms LifetimeCount=30 MaxKeepAliveCount=10
Client 侧还需设置: RequestedSessionTimeout=60000ms SecureChannel 证书信任
订阅相比轮询的优势是只传变化值(Deadband 死区可过滤小幅波动,如死区 0.5℃),在 5000 点位的场景下能把上行量降低一个数量级。代价是要处理会话断线重连:UA 会话超时后需要重新创建订阅并重建 MonitoredItem,重连时务必带上订阅的客户端句柄做幂等恢复,否则会出现重复订阅导致数据翻倍。选型上,如果设备支持 UA 就优先用 UA,只有老旧串口设备才退回 Modbus。
6. 北向协议转换与 MQTT 桥接
北向的事实标准是 MQTT,原因是它天然支持断线重连、遗嘱消息与三级 QoS,且开销小。转换的核心工作是点位到主题的映射与语义补齐。
推荐的主题结构(设备侧发布)与网关侧桥接配置:
{productKey}/{deviceName}/property/post 属性上报(JSON 载荷)
{productKey}/{deviceName}/event/{eventId}/post 事件上报
{productKey}/{deviceName}/property/set 云端属性下发
connection cloud-bridge # mosquitto 桥接示例:把本地 broker 数据桥到云端
address mqtts://iot.example.com:8883
topic "factory/#" out 1
bridge_cafile /etc/mosquitto/ca.crt
bridge_certfile /etc/mosquitto/gw01.crt
bridge_keyfile /etc/mosquitto/gw01.key
remote_clientid gw01-line3
cleansession false
max_queued_messages 100000
queue_qos0_messages true
autosave_interval 30
几个关键参数:cleansession false 让云端保留会话与离线消息;max_queued_messages 决定本地积压上限(默认 100,实际要调到几万);QoS 选 1 而不是 2,因为 QoS 2 的四次握手在高延迟链路上会显著放大往返次数。转换层还要负责时间戳:边缘在采集时刻打 ts,云端收到时另记 ingest_ts,两者差值就是链路延迟,是排查积压的第一手指标。
7. 本地缓存与断网续传
断网续传的工程本质是一个持久化队列加一个投递状态机。设计要点:
- 存储介质:优先 eMMC 或 SSD,避免频繁写 SD 卡(写放大与掉电损坏)。队列目录建议单独分区,防止写满拖垮根分区。
- 容量上限:按「日均上行量 × 期望离线小时数 × 2」估算。例如日均 50MB、期望扛 72 小时,则配额 300MB。
- 溢出策略:队列满时按优先级丢弃,规则是「先丢统计类、保留告警类」,绝不静默丢弃。
- 投递语义:至少一次(at-least-once)实现简单,配合消息 ID 去重实现业务上的恰好一次。云端用
deviceName + msgId建唯一索引做幂等。 - 顺序:同一设备的消息必须保序,否则云端会看到时间倒流。可用单分区队列加顺序消费。
- 重放速率:恢复后不能全速重放,否则瞬间打满链路并压垮云端。典型做法是限速到正常速率的 2~5 倍,并带指数退避。
sink: # 本地队列(eKuiper + 磁盘缓冲后端)关键配置
mqtt:
server: "tcp://127.0.0.1:1883"
topic: "factory/line3/data"
qos: 1
sendSingle: true
buffer:
type: "disk"
maxSize: 300MB
flushIntervalMs: 1000
resendIntervalMs: 5000
resendMultiplier: 2
云端侧的去重表设计(msgId 由边缘生成,全局唯一):
CREATE TABLE iot_reading (
device_id VARCHAR(64) NOT NULL,
msg_id VARCHAR(64) NOT NULL,
ts TIMESTAMP NOT NULL,
payload JSONB NOT NULL,
PRIMARY KEY (device_id, msg_id)
);
CREATE INDEX idx_reading_ts ON iot_reading (device_id, ts DESC);
插入时用 INSERT ... ON CONFLICT DO NOTHING,重放产生的重复消息会被主键吞掉,业务上等价于恰好一次。索引建在 (device_id, ts) 上是为了让「补传数据按时间回填」和「最近一小时查询」都能走索引,否则补传会把时序库的写入放大数倍。
8. 边缘规则与流处理
边缘规则引擎的职责是把「连续上报的原始点位」变成「有业务含义的事件」。主流选择是 eKuiper(Go,资源占用小,支持 SQL 风格规则)与 Node-RED(低代码拖拽,适合做集成原型)。
eKuiper 规则示例:每 10 秒统计一次电机温度均值,超过阈值则输出告警,并做 5 秒去抖:
SELECT
avg(temperature) AS avg_temp,
count(*) AS samples,
meta("deviceName") AS device
FROM
"factory/line3/motor/+/data"
GROUP BY
TUMBLINGWINDOW(ss, 10)
HAVING
avg(temperature) > 85
规则设计上要注意三点:一是窗口类型选择,滚动窗口(Tumbling)不重叠、滑动窗口(Hopping)平滑、会话窗口适合间歇上报;二是状态存储,带聚合的规则必须配置状态后端(内存或 SQLite),否则重启后窗口丢失;三是规则数量控制,几十条规则没问题,几百条就要评估 CPU 与内存,必要时下沉到设备侧做一级过滤。
Node-RED 的优势是与大量南向节点的现成集成和可视化调试,缺点是高吞吐下性能有限(单实例通常几千 msg/s),且流程以 JSON 存储,版本管理与代码评审困难。实践中常见组合是「Node-RED 做协议对接与快速验证,稳定后迁移到 eKuiper 或自研服务」。
9. 边缘本地推理
边缘推理的目标不是精度最高,而是在受限算力下满足延迟与成本。三条技术路线:
- ONNX Runtime:跨平台、CPU 优化好,支持 EP(Execution Provider)如 OpenVINO、CUDA、CoreML。适合 x86 工控机与 ARM64 网关。
- TFLite / LiteRT:面向 MCU 与低端 ARM,配合 int8 量化后模型可压到几百 KB,适合 RK 系列与树莓派。
- OpenVINO:Intel 平台专用,在 N100/N305 这类低功耗 x86 上能拿到接近 GPU 的吞吐。
模型优化的顺序建议是:先量化(fp32 → int8,体积降 4 倍、速度提升 2~4 倍,精度损失通常 <1%),再剪枝,最后考虑蒸馏。输入分辨率是最容易被忽略的杠杆:从 640×640 降到 320×320,计算量降 4 倍,对小目标检测精度影响可控。
部署时要处理三个工程问题:模型文件的版本管理与热加载(不能重启进程)、推理与采集的资源隔离(推理占满 CPU 会让 Modbus 轮询超时,建议绑核或限制线程数)、以及推理失败的降级路径(模型加载失败时回退到规则判断,而不是整个网关不可用)。
10. 硬件选型与工业防护
选型先看三个维度:算力需求、环境温度、供电与接口。
| 平台 | 算力 | 典型功耗 | 价格区间 | 适用场景 |
|---|---|---|---|---|
| 树莓派 4/5 | 4 核 A72/A76 | 5~12W | 400~800 元 | 原型、室内轻量网关 |
| RK3588 | 8 核 + 6TOPS NPU | 10~25W | 800~2000 元 | 视觉推理、多路视频 |
| Jetson Orin Nano/NX | 20~100 TOPS | 10~25W | 2000~6000 元 | 多路视觉、边缘 AI |
| 工业宽温 PC(x86) | i3/i5/N100 | 15~60W | 2000~8000 元 | 严苛环境、多协议 |
工业现场必须关注:宽温(-4085℃ 存储、-2070℃ 运行,消费级只保证 050℃)、看门狗(硬件 WDT 必须真的接到复位,否则死机要人工上电)、RTC 电池(掉电后时间不能丢,否则断网续传的时间戳全错)、隔离 IO(RS485 与数字量输入要有 2.5kV 光耦或磁隔离,避免地环流烧口)、电源(936V 宽压输入、防反接、防浪涌)。另外要留硬件恢复手段:一个物理复位键、一个 USB 恢复口,比远程调试省下大量现场出差成本。
11. 软件栈、容器化与资源限制
边缘软件栈的分层建议:底层用 balenaOS 或 Ubuntu Core 这类支持原子更新与只读根分区的系统,中间用 Docker/containerd 跑应用容器,编排视规模选 K3s(多节点)或 systemd(单节点)。
防止 eMMC 写坏的关键是只读根文件系统加 overlayfs,把日志、队列、数据库等可变数据放到独立可写分区,并对日志做轮转与大小上限:
services: # docker-compose.yml 片段:边缘应用与资源限制
gateway:
image: registry.example.com/edge/gateway:1.8.3
restart: unless-stopped
cpus: "1.5"
mem_limit: 512m
memswap_limit: 512m
pids_limit: 256
read_only: true
tmpfs:
- /tmp:size=32m
volumes:
- /data/gateway:/var/lib/gateway
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
资源限制依赖 cgroup 的 CPU、内存与 PID 子系统实现,隔离原理与 namespace 细节见 Linux 容器与隔离机制
。这不是可选项:边缘节点上通常同时跑协议接入、规则引擎、推理与日志上报,任一容器内存泄漏都会拖垮整机。--cpus 与 --memory 之外,还要限制 pids_limit(防止 fork 炸弹)与日志大小(防止磁盘写满)。K3s 场景下可用 ResourceQuota 与 LimitRange 做命名空间级兜底。
12. 边缘与云协同
云边协同要设计四件事:
- 上行通道:主用有线或 4G,备用另一运营商。双上行建议做策略路由而不是简单 failover,因为主链路「通但慢」比「断」更难发现。健康检查要基于应用层心跳而非 ping。
- 离线队列策略:明确容量上限、溢出丢弃优先级与恢复后的重放速率,并在云端暴露「设备积压深度」指标。
- 时钟同步:边缘无 GPS 时用 NTP 或 PTP 与云端对齐,误差应控制在 100ms 内;时间不同步会让窗口聚合与断网补传的数据乱序。
- 边缘侧影子:边缘维护一份本地设备影子(desired/reported),断网期间仍能响应控制指令并记录,恢复后与云端影子做版本合并。具体机制可参考 设备管理与设备影子 。
北向通道的主题命名、QoS 选择与遗嘱消息设计细节见 MQTT 协议与物联网消息模型 ;边缘节点自身的固件与容器更新通道见 OTA 固件升级与差分更新 。
13. 边缘节点可观测性
边缘节点数量多、位置分散、经常无人值守,可观测性必须按「能远程定位问题」来设计,而不是照搬云端那一套。
指标分四组:
- 资源:CPU 使用率、内存可用量、磁盘剩余、温度(超过 70℃ 要告警,很多工控机在 80℃ 会降频)。
- 业务:采集成功率、上行成功率、队列深度、规则触发次数、推理 P99。
- 链路:MQTT 连接状态与重连次数、RTT、丢包率、当前使用的上行通道。
- 生命周期:运行时长、重启次数、上次 OTA 版本与结果。
队列深度和重连次数是两张最有价值的「先行指标」:队列持续增长说明上行在恶化,重连次数突增说明链路不稳定,二者都比「设备离线」告警早出现几分钟到几小时。
日志方面,边缘必须做本地轮转(单文件 10MB、保留 3 份),并且只把 ERROR 级别实时上行,DEBUG 按需拉取,否则日志本身就会吃满带宽。一个实用的做法是给每个日志加 deviceName 与 traceId 字段,云端按 traceId 关联设备端与云端处理链路。健康检查接口建议暴露 /healthz,返回队列深度、上次上行时间与版本号,运维用一条 curl 就能判断节点状态。
curl -s http://127.0.0.1:8080/healthz
{"status":"ok","queue_depth":12,"last_uplink":"2026-10-07T03:28:41+08:00","fw":"1.8.3","uptime_s":864213}
权衡取舍
| 决策点 | 方案 A | 方案 B | 建议 |
|---|---|---|---|
| 协议接入 | 网关统一转换 | 设备直连云 | 设备支持 MQTT 且网络可靠时直连;异构现场用网关 |
| 计算位置 | 边缘推理 | 云端推理 | 延迟 <100ms 或数据不能出厂的走边缘,其余走云 |
| 编排 | Docker + systemd | K3s | 单节点用 systemd 更简单;超过 5 个应用或需滚动升级用 K3s |
| 文件系统 | 可写根分区 | 只读 + overlayfs | 生产环境一律只读,开发阶段可写 |
| 缓存介质 | SD 卡 | eMMC/SSD | SD 卡仅用于原型,生产必须换 eMMC |
| 上行语义 | at-least-once + 去重 | exactly-once | 边缘场景几乎总是前者,后者成本过高 |
| 规则引擎 | eKuiper | Node-RED | 高吞吐与版本化选 eKuiper,快速集成选 Node-RED |
常见坑清单
- 寄存器地址偏移错:现象是读到全 0 或异常码 02,原因是 1-based 手册地址未减 1,规避方法是对照报文抓包核对首地址。
- Modbus 轮询超时拖垮总线:现象是整条总线数据断续,原因是单台从站无响应且重试次数过多,规避方法是缩短超时、限制重试并把故障从站隔离。
- OPC UA 重连后数据翻倍:现象是同一测点每秒出现两次,原因是重连时未清理旧订阅,规避方法是按客户端句柄幂等重建订阅。
- SD 卡写坏导致网关变砖:现象是运行数周后无法启动,原因是日志与队列高频写 SD 卡,规避方法是换 eMMC、只读根分区并限制日志大小。
- 断网恢复后雪崩:现象是链路恢复瞬间云端被打满,原因是全速重放积压队列,规避方法是限速重放加指数退避。
- 边缘容器占满内存:现象是协议接入进程被杀,原因是推理容器无内存上限,规避方法是设置 mem_limit 并绑定 CPU 亲和。
- RTC 电池失效:现象是补传数据时间戳集中在开机时刻,原因是 RTC 掉电归零且未做 NTP 校时,规避方法是更换电池并加启动校时。
- QoS 2 放大延迟:现象是高延迟链路上吞吐骤降,原因是四次握手往返,规避方法是改用 QoS 1 并在应用层去重。
- 看门狗未真正接复位:现象是死机后必须人工上电,原因是 WDT 只在日志里报警,规避方法是验证喂狗中断后硬件确实复位。
小结
边缘层的价值可以归结为一句话:把「必须在本地发生的事」留在本地,把「必须全局决策的事」交给云端。判断标准是延迟约束、带宽成本、断网可用性与数据合规这四条,任何一条触发就该引入边缘节点。网关的能力从协议转换起步,逐步叠加缓存续传、规则流处理与本地推理,每加一层都对应一类新的运维复杂度,需要同步建设版本管理与灰度通道。
工程上最容易被低估的是存储与时间这两件事。缓存队列的介质、容量与溢出策略决定了断网时业务能否兜住;时钟同步决定了补传数据能不能与云端数据正确对齐。这两点在实验室里几乎不会暴露,但会在现场运行数周后集中爆发。
下一步建议先补齐设备侧的身份与协议基础,再回来看云边状态同步:设备影子的文档模型与版本合并策略决定了断网期间的控制指令能否正确收敛,这部分在下一篇文章中展开。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。