3、差分升级技术:全量升级 vs 差分升级、bsdiff/bspatch原理、差分算法选型与压缩率对比
说到物联网设备的固件升级,很多刚入行的朋友第一反应就是「把新固件整个发过去不就行了?」。嗯,这种做法叫全量升级,确实简单粗暴。但等你真正部署到成千上万个设备上,就会发现——带宽扛不住、流量费吓人、用户等得想砸设备。
我最早做智能电表项目时,就吃过这个亏。一个固件包8MB,设备用2G网络下载,每次升级要花十几分钟。用户投诉说「电表死机了」,其实是在升级。后来换成差分升级,升级包从8MB压缩到300KB,升级时间缩短到1分钟以内。嗯,效果立竿见影。
全量升级 vs 差分升级
先说说两者的本质区别。
全量升级:把完整的固件镜像发过去。不管设备上当前跑的是哪个版本,直接覆盖。优点是实现简单,缺点是每次升级都要传输整个固件。
差分升级:只传输新旧固件之间的差异部分。设备收到差分补丁后,在本地用旧固件「拼」出新固件。优点是传输量极小,缺点是需要额外的计算和存储空间。
我个人的经验是:如果设备存储空间充裕(比如有2倍固件大小的Flash),网络带宽又有限,差分升级几乎是必选项。你想想看,一个100KB的补丁包和一个8MB的全量包,哪个更香?
核心对比表
| 对比维度 | 全量升级 | 差分升级 |
|---|---|---|
| 传输数据量 | 大(整个固件) | 小(仅差异部分) |
| 实现复杂度 | 低 | 高(需差分算法) |
| 设备端要求 | 低(只需写入) | 高(需计算+临时存储) |
| 适用场景 | 首次烧录、重大版本变更 | 小版本迭代、OTA升级 |
| 升级成功率 | 高 | 中等(依赖旧固件完整性) |
bsdiff/bspatch 原理
说到差分算法,就绕不开 bsdiff 和 bspatch。这是 Colin Percival 在2003年提出的经典方案,至今仍是嵌入式领域的主流选择。
bsdiff 的核心思路是:找到新旧两个二进制文件之间的最长公共子序列。说白了,就是找出哪些字节没变、哪些变了、哪些是新增的。
具体流程是这样的:
- 扫描旧固件:建立后缀数组,快速定位重复片段
- 对比新固件:用旧固件的后缀数组,找出新固件中哪些部分已经在旧固件里出现过
- 生成差分补丁:记录三部分信息——「从旧固件复制的数据」、「新增的数据」、「控制指令」
bspatch 在设备端做逆向操作:
- 读取控制指令
- 根据指令从旧固件复制数据
- 插入新增数据
- 拼装成完整的新固件
我曾经在一个智能门锁项目里用过 bsdiff。门锁的MCU只有256KB Flash,固件每次升级只改几个功能模块。bsdiff生成的补丁包平均只有原始固件的5%-15%,效果非常好。但要注意——设备端必须保留完整的旧固件,否则 bspatch 无法工作。
小提示:bsdiff 生成的补丁包大小,跟新旧固件的相似度直接相关。如果两个版本改动超过30%,差分优势就不明显了。我一般建议:小版本迭代用差分,大版本重构用全量。
差分算法选型与压缩率对比
市面上常见的差分算法不止 bsdiff 一种。我整理了几个主流方案的对比,供你参考。
| 算法 | 压缩率 | 计算开销 | 内存占用 | 适用场景 |
|---|---|---|---|---|
| bsdiff | 高(5%-20%) | 高(生成端) | 高(需大量内存) | 资源丰富的设备 |
| hdiffpatch | 中(10%-30%) | 中 | 中 | 通用场景 |
| xdelta | 中(15%-35%) | 低 | 低 | 资源受限设备 |
| 自定义差分 | 取决于实现 | 可控 | 可控 | 特定业务场景 |
为什么 bsdiff 压缩率最高?因为它用了后缀数组做全局匹配。但代价是生成补丁时需要大量内存——一个8MB的固件,生成过程可能要占用几百MB内存。这在服务器端不是问题,但在设备端做差分生成就不可行了。
我个人的选型建议:
- 服务器端生成、设备端应用:首选 bsdiff,压缩率最高
- 设备端也需要生成差分:选 xdelta 或 hdiffpatch,计算开销小
- 固件结构固定、改动模式明确:可以考虑自定义差分算法,比如只对比特定段
避坑指南:我曾经在一个项目中选了 bsdiff,但没注意设备端的 RAM 限制。bspatch 运行时需要额外分配一块跟固件大小相当的缓冲区,结果设备内存不够,直接 OOM 重启。后来改用 xdelta,虽然压缩率差一些,但设备端跑得稳稳的。
所以,选算法前一定要先评估设备端的资源:RAM 够不够?Flash 有没有双倍空间?CPU 能不能跑动解压逻辑?
压缩率实测数据
拿我最近做的一个智能家居网关项目举例。固件从 v2.1.0 升级到 v2.1.3,改动量大约8%。
| 升级方式 | 传输大小 | 压缩率 | 设备端耗时 |
|---|---|---|---|
| 全量升级(未压缩) | 4.2 MB | 100% | 约30秒 |
| 全量升级(gzip压缩) | 1.8 MB | 43% | 约15秒 |
| bsdiff 差分 | 0.3 MB | 7% | 约8秒 |
| xdelta 差分 | 0.5 MB | 12% | 约5秒 |
看到没?bsdiff 把传输量压缩到了全量的7%,效果非常惊人。但设备端耗时反而比 xdelta 长,因为 bspatch 的解压逻辑更复杂。所以实际选型时,要在「传输量」和「设备端性能」之间做权衡。
嗯,最后说一句:差分升级不是银弹。如果你的设备经常需要跨大版本升级(比如从 v1.0 直接跳到 v5.0),差分补丁反而会很大,甚至比全量还大。我一般会设计一个策略:小版本用差分,大版本用全量,两者结合使用。
好了,差分升级的核心内容就这些。下一章我们聊聊如何把差分升级集成到 OTA 系统中,包括补丁管理、版本回退、断点续传等实战问题。