4、增量包生成策略:全量包 vs 增量包,何时选择哪种策略?

做OTA升级,最核心的问题就是:到底给设备发什么?

是把整个固件都扔过去,还是只发变化的部分?

这个问题,我几乎每次做新项目都会被问到。说白了,这就是全量包和增量包的选择题。今天咱们就把它彻底聊透。

4.1 全量包:简单粗暴,但很稳

全量包,顾名思义,就是完整的固件镜像。从V1.0升级到V2.0,我把整个V2.0的二进制文件发给你。

优点很明显:

  • 实现简单:服务器不用做任何差分计算,直接下发文件就行。
  • 可靠性高:设备端拿到文件直接刷写,不依赖旧版本的状态。哪怕设备当前版本损坏了,全量包也能救回来。
  • 兼容性好:不管设备当前是V1.0还是V1.5,只要目标版本是V2.0,同一个全量包都能用。

缺点也扎心:

  • 体积大:一个固件动辄几十MB甚至上百MB。在2G/3G网络下,下载时间很长,流量消耗也大。
  • 升级窗口长:下载慢,刷写也慢。设备可能长时间处于不可用状态。

我的经验:全量包适合什么场景?

  • 首次升级:设备出厂后第一次OTA,必须用全量包。因为旧版本未知,没法做差分。
  • 跨版本升级:比如从V1.0直接跳到V5.0。中间版本差异太大,增量包可能比全量包还大。
  • 设备存储空间充裕:如果设备有256MB Flash,一个50MB的全量包不算什么。
  • 网络条件好:Wi-Fi环境下,全量包下载也就几秒钟,没必要折腾增量。

4.2 增量包:精打细算,但有门槛

增量包,只包含新旧版本之间的差异部分。比如V1.0到V1.1只改了一个驱动文件,那增量包可能只有几百KB。

优点:

  • 体积小:通常只有全量包的10%~30%。对于NB-IoT、2G这类窄带网络,这是巨大的优势。
  • 升级快:下载快,刷写也快。设备离线时间短。
  • 节省流量:用户不用为OTA支付高额流量费。

缺点:

  • 依赖旧版本:增量包只能从特定版本升级。设备当前版本必须和生成增量包时的基准版本一致。
  • 生成复杂:需要bsdiff、hdiffpatch等差分工具。而且差分算法对内存和CPU有要求。
  • 容错性差:如果设备当前固件有损坏,增量包打上去可能直接变砖。

我曾经踩过的坑:

有一次,我们给一批设备推送增量包。结果有5%的设备升级后反复重启。查了两天才发现,这些设备的Flash有坏块,导致旧固件在某个地址上数据不对。增量包打上去后,校验失败,系统直接崩溃。

从那以后,我要求所有增量升级前,必须先做固件完整性校验。如果校验失败,自动回退到全量包方案。

4.3 核心对比:一张表说清楚

对比维度 全量包 增量包
包体大小 大(完整固件) 小(仅差异部分)
生成难度 低(直接打包) 高(需差分工具)
版本依赖 无(任意版本可用) 强(必须基于特定版本)
升级可靠性 高(可修复损坏固件) 中(依赖旧固件完整性)
网络消耗
适用场景 首次升级、跨版本、Wi-Fi环境 小版本迭代、窄带网络、流量敏感

4.4 我的选择策略:动态决策

我个人习惯,不会死板地只用一种策略。而是根据实际情况动态选择。

具体做法是这样的:

  1. 版本差异分析:服务器端比较新旧固件,计算差异大小。
  2. 设定阈值:如果增量包大小 > 全量包的70%,那就直接用全量包。因为差分算法本身有开销,差异太大时增量包反而没优势。
  3. 网络感知:如果设备当前在Wi-Fi下,优先用全量包。如果在移动网络下,优先用增量包。
  4. 版本链管理:维护一个版本升级路径表。比如V1.0→V1.1→V1.2,每个相邻版本都生成增量包。同时保留从V1.0直接到V1.2的全量包。

一个小技巧:

我建议在设备端也保留一个升级策略缓存。比如设备上次升级失败了,下次请求时带上失败原因。服务器根据这个信息,自动切换为全量包重试。这样能大幅提高升级成功率。

4.5 代码示例:增量包生成脚本

这里给一个我常用的增量包生成脚本,基于bsdiff工具:

#!/bin/bash
# 增量包生成脚本
# 用法:./gen_diff.sh old_firmware.bin new_firmware.bin output.patch

OLD_FILE=$1
NEW_FILE=$2
PATCH_FILE=$3

# 检查输入文件
if [ ! -f "$OLD_FILE" ] || [ ! -f "$NEW_FILE" ]; then
    echo "错误:输入文件不存在"
    exit 1
fi

# 生成差分补丁
bsdiff "$OLD_FILE" "$NEW_FILE" "$PATCH_FILE"

# 检查补丁大小
OLD_SIZE=$(stat -c%s "$OLD_FILE")
PATCH_SIZE=$(stat -c%s "$PATCH_FILE")
THRESHOLD=$((OLD_SIZE * 70 / 100))

echo "旧固件大小: $OLD_SIZE bytes"
echo "补丁大小: $PATCH_SIZE bytes"

if [ "$PATCH_SIZE" -gt "$THRESHOLD" ]; then
    echo "警告:补丁大小超过全量包的70%,建议使用全量包"
    # 这里可以自动切换为全量包策略
    cp "$NEW_FILE" "$PATCH_FILE"
    echo "已切换为全量包: $PATCH_FILE"
fi

echo "增量包生成完成"

4.6 避坑指南

最后,分享几个我亲身踩过的坑:

  • 不要忽略Flash磨损:有些设备Flash有擦写次数限制。频繁用增量包升级,每次只改一小块区域,可能导致那块Flash提前报废。我建议全量包和增量包交替使用,均衡磨损。
  • 注意字节对齐:差分算法生成的补丁,在应用到设备时,必须保证地址对齐。我曾经遇到过因为4字节对齐问题,导致升级后部分功能异常。
  • 保留回退能力:无论用哪种策略,设备端一定要保留至少两个固件副本:当前运行版本和备份版本。这样升级失败时,还能回退到上一个可用版本。

嗯,关于全量包和增量包的选择,今天就聊这么多。你想想看,其实没有绝对的好坏,关键看你的设备、网络和用户场景。我个人更倾向于动态混合策略,既保证可靠性,又兼顾效率。

下一章,咱们聊聊差分算法的具体实现,以及如何优化补丁大小。到时候我会分享一些bsdiff的调参经验,保证让你少走弯路。