4、OTA更新协议与架构:MQTT/CoAP/HTTP协议对比、更新服务器架构、断点续传与差分更新

OTA(Over-the-Air)更新,说白了就是给设备“打补丁”。但物联网设备不像手机,它们可能只有几百KB内存,网络时断时续,甚至还在用电池供电。选错协议或架构,更新就是一场灾难。

我个人习惯把OTA更新拆成两个层面看:传输协议和服务器架构。协议决定数据怎么传,架构决定系统怎么扛。下面我们一个一个聊。

4.1 三大传输协议对比:MQTT vs CoAP vs HTTP

先看一张对比表,心里有个底:

特性 MQTT CoAP HTTP
传输层 TCP(长连接) UDP(支持可靠传输) TCP(短连接为主)
消息模型 发布/订阅 请求/响应 + 观察 请求/响应
头部开销 2字节最小 4字节最小 数百字节
QoS支持 0/1/2三级 CON/NON/ACK/RST 无原生QoS
适用场景 低带宽、不可靠网络 极低功耗、受限节点 宽带、高算力设备
安全扩展 TLS + 用户名/密码 DTLS + 预共享密钥 TLS + 证书

4.2 MQTT:我最常用的协议

MQTT是我在项目中用得最多的协议。为什么?因为它天生为“不可靠网络”设计。

发布/订阅模型让服务器不用知道每个设备在哪。设备上线后订阅一个更新主题,服务器往主题里发一条消息,所有设备都能收到。这在批量更新时特别爽。

QoS(服务质量)是MQTT的杀手锏。我建议OTA更新至少用QoS 1:

  • QoS 0:最多发一次,丢了不重传。适合心跳,不适合固件。
  • QoS 1:至少发一次,可能重复。配合固件校验,够用。
  • QoS 2:恰好发一次,开销大。我一般不用,除非合规要求。
我的经验:MQTT的遗嘱消息(Last Will)可以用来标记设备离线。如果设备更新到一半掉线,服务器能立刻知道,避免“僵尸设备”占用更新队列。

4.3 CoAP:受限设备的首选

CoAP跑在UDP上,头部只有4字节。你想想看,一个内存只有32KB的传感器,跑HTTP简直是奢望。CoAP就是为这种场景生的。

CoAP的观察模式很有意思。设备可以“订阅”一个资源,服务器有更新时主动推送。这和MQTT的发布/订阅有点像,但更轻量。

不过要注意,CoAP的可靠传输靠的是CON(确认消息)和重传。我在一个项目中遇到过,设备在弱网环境下反复重传CON消息,把电池耗光了。后来我们加了指数退避,问题才解决。

避坑指南:CoAP的DTLS握手在UDP上容易丢包。我曾经见过一个设备,DTLS握手要重试5次才能成功,每次耗时3秒。建议在固件更新前先做一次“连接预检”,确认DTLS通道稳定再开始传输。

4.4 HTTP:简单但“重”

HTTP的优势是简单。设备发一个GET请求,服务器返回固件。但它的头部开销大,而且短连接在批量更新时容易造成“惊群效应”——所有设备同时请求,服务器瞬间被打爆。

HTTP适合宽带、高算力的设备,比如智能网关、边缘服务器。我建议用HTTP/2,它支持多路复用,能减少连接数。

另外,HTTP的Range请求头天然支持断点续传。这个后面会细说。

4.5 更新服务器架构

协议选好了,服务器怎么搭?我画了一张架构图,你看一眼就明白:

OTA更新服务器架构 设备A 设备B 设备C 设备D 协议网关层(MQTT Broker / CoAP Proxy / HTTP LB) 协议转换 · 连接管理 · 流量整形 更新调度层(Update Orchestrator) 分批策略 · 版本管理 · 回滚控制 固件存储(对象存储) 差分包 · 全量包 · 元数据 状态数据库 设备状态 · 更新进度 · 错误日志 架构要点:协议网关层负责设备接入,更新调度层控制节奏,存储层保存固件和状态

架构分三层:

  • 协议网关层:负责设备接入。MQTT Broker、CoAP Proxy、HTTP负载均衡器都在这一层。我建议网关层做流量整形,比如限制每台设备的最大下载速度,防止网络被占满。
  • 更新调度层:核心逻辑在这里。它决定“谁先更新”、“什么时候更新”、“失败了怎么办”。我习惯用分批策略,先更新10%的设备,观察24小时没问题再全量推送。
  • 存储层:固件包和状态数据分开存。固件包用对象存储(比如MinIO或S3),状态数据用关系型数据库或Redis。这样查询状态时不会影响固件下载。

4.6 断点续传:别让设备从头再来

物联网设备经常在弱网环境下工作。下载到一半断网了,如果从头开始,用户会疯掉。

断点续传的核心是记录已接收的字节偏移量。设备每次请求时带上偏移量,服务器从偏移量开始继续发送。

HTTP的Range请求头天然支持:

GET /firmware/v2.1.bin HTTP/1.1
Host: ota.example.com
Range: bytes=1048576-

服务器返回206 Partial Content,从第1MB开始发。

MQTT和CoAP没有原生断点续传。我一般自己实现:

  • 设备每收到一块数据(比如64KB),就向服务器上报offset。
  • 服务器记录在状态数据库里。
  • 重连后,设备从最后一个确认的offset开始请求。
关键点:断点续传的“断点”必须持久化。设备断电后重启,如果断点存在RAM里,那就白搭了。我建议把断点写入Flash或NVM,每次上报成功后同步一次。

4.7 差分更新:省流量、省时间

差分更新(Delta Update)只传输固件变化的部分。比如固件从2.0升到2.1,可能只改了10KB,差分包就只传10KB,而不是整个2MB的固件。

常用的差分算法有:

  • bsdiff:基于二进制差分,压缩率高,但计算开销大。适合服务器端生成差分包。
  • HDiffPatch:比bsdiff快,内存占用低。我在资源受限的网关上用过,效果不错。
  • xdelta:基于VCDIFF标准,适合文本或结构化数据。

差分更新的流程:

  1. 服务器根据设备当前版本,生成差分包。
  2. 设备下载差分包。
  3. 设备用本地旧固件 + 差分包,合成新固件。
  4. 校验新固件的哈希值,确认无误后写入。
避坑指南:差分更新依赖旧固件的完整性。如果旧固件被篡改或损坏,合成出来的新固件也是坏的。我建议在合成前先校验旧固件的哈希值,确保它没被改过。

另外,差分更新不是万能的。如果固件版本跨度太大(比如从1.0直接升到3.0),差分包可能比全量包还大。我一般设定一个阈值:差分包超过全量包的80%时,直接下发全量包。

4.8 小结

协议选型没有银弹。MQTT适合批量推送,CoAP适合极低功耗设备,HTTP适合高算力网关。服务器架构要分层解耦,断点续传和差分更新是提升更新成功率的两个关键手段。