3、更新架构设计:C/S架构 vs P2P架构、更新服务器部署、带宽优化策略、断点续传机制
更新架构选型,说白了就是回答一个问题:更新包怎么从服务器跑到设备上?
我见过太多团队,上来就拍脑袋选C/S,结果几千台设备同时拉包,带宽直接被打满。也见过迷信P2P的,结果内网设备互相找不到,更新比蜗牛还慢。
这一章,我把两种架构的实战经验、部署要点、带宽优化和断点续传机制,一次性讲透。
3.1 C/S架构 vs P2P架构:怎么选?
先看一张对比图,帮你快速建立认知。
两种架构各有适用场景。我直接给你一个决策表,照着选就行。
| 对比维度 | C/S 架构 | P2P 架构 |
|---|---|---|
| 设备数量 | ≤ 500 台 | ≥ 1000 台 |
| 网络环境 | 公网/专线 | 内网/局域网 |
| 更新包大小 | ≤ 100 MB | ≥ 500 MB |
| 带宽成本 | 高(服务器出口) | 低(设备间分摊) |
| 部署复杂度 | 低 | 高 |
| 更新成功率 | 高(可控) | 中(依赖节点数量) |
我的建议:如果设备少于500台,老老实实用C/S。别折腾P2P,收益不大,坑不少。超过1000台,尤其是内网环境,P2P能帮你省下80%的带宽成本。
3.2 更新服务器部署:实战要点
不管选哪种架构,更新服务器都是核心。我分享几个部署经验。
3.2.1 服务器选型与配置
我个人习惯用 Nginx 做静态文件服务器,配合健康检查。配置很简单:
server {
listen 443 ssl;
server_name update.example.com;
# SSL 配置(必须)
ssl_certificate /etc/nginx/certs/update.crt;
ssl_certificate_key /etc/nginx/certs/update.key;
# 更新包存放目录
root /data/updates;
# 限速:每连接 5MB/s
limit_rate 5m;
# 断点续传支持
location / {
add_header Accept-Ranges bytes;
}
# 访问日志
access_log /var/log/nginx/update_access.log;
error_log /var/log/nginx/update_error.log;
}
注意:限速一定要做。我曾经遇到过,没限速,一台设备跑满千兆带宽,其他设备全部超时。限速值根据设备数量和总带宽计算,一般每连接 1-5 MB/s 比较合理。
3.2.2 高可用部署
单点故障是运维的噩梦。我建议至少部署两台服务器,用 DNS 轮询或负载均衡器分发。
架构图如下:
关键点:
- 共享存储:更新包放在 NFS 或 MinIO 上,两台服务器都能访问
- 健康检查:负载均衡器每 5 秒检查一次 /health 端点
- 会话保持:建议关闭,设备断线重连后可以切换到另一台服务器
3.3 带宽优化策略
带宽是钱,尤其是公网带宽。我总结了几条实战策略。
3.3.1 增量更新
全量更新是带宽杀手。我建议用 bsdiff 或 xdelta 做增量包。
举个例子:一个 500MB 的固件,每次只改了几个文件。增量包可能只有 10-50MB。带宽节省 90% 以上。
# 生成增量包(服务端)
bsdiff old_firmware.bin new_firmware.bin update.patch
# 应用增量包(设备端)
bspatch old_firmware.bin new_firmware.bin update.patch
经验之谈:增量更新不是万能的。如果版本跨度太大(比如 v1.0 直接升 v5.0),增量包可能比全量包还大。我一般限制最多跨 3 个版本做增量,超过就全量更新。
3.3.2 分时段限速
白天业务高峰期,带宽要留给核心业务。晚上再放开更新。
我习惯用脚本控制:
# 白天(08:00-20:00):每连接限速 1MB/s
# 晚上(20:00-08:00):每连接限速 10MB/s
if [[ $(date +%H) -ge 8 && $(date +%H) -lt 20 ]]; then
RATE="1m"
else
RATE="10m"
fi
# 更新 Nginx 限速配置
sed -i "s/limit_rate .*/limit_rate $RATE;/" /etc/nginx/conf.d/update.conf
nginx -s reload
3.3.3 分片下载
大文件分片下载,配合 P2P 效果更好。每个设备只下载一部分,然后互相交换。
分片策略:
- 分片大小:建议 1-4 MB,太小了元数据开销大,太大了分享效率低
- 分片校验:每个分片带 SHA256 哈希,防止数据损坏
- 分片优先级:先下载关键分片(如引导程序),再下载普通分片
3.4 断点续传机制
设备更新最怕什么?下载到一半断网了,又要重头开始。断点续传是刚需。
3.4.1 HTTP 断点续传
HTTP 协议原生支持断点续传,用 Range 头就行。Nginx 默认支持,但需要确认配置。
设备端请求示例:
# 第一次请求:下载整个文件
GET /firmware/v5.0.bin HTTP/1.1
Host: update.example.com
# 服务器响应:200 OK + 完整文件
# 断线后重连:只下载剩余部分
GET /firmware/v5.0.bin HTTP/1.1
Host: update.example.com
Range: bytes=1048576- # 从 1MB 处开始下载
# 服务器响应:206 Partial Content + 剩余数据
3.4.2 设备端实现要点
设备端代码要注意几个坑:
# Python 示例:带断点续传的下载器
import requests
import os
def download_with_resume(url, local_path):
headers = {}
# 检查本地已有文件大小
if os.path.exists(local_path):
existing_size = os.path.getsize(local_path)
headers['Range'] = f'bytes={existing_size}-'
else:
existing_size = 0
# 发起请求
response = requests.get(url, headers=headers, stream=True)
# 检查服务器是否支持断点续传
if response.status_code == 206:
print(f"断点续传:从 {existing_size} 字节继续")
elif response.status_code == 200:
print("重新下载完整文件")
existing_size = 0
else:
print(f"下载失败:{response.status_code}")
return False
# 写入文件(追加模式)
mode = 'ab' if existing_size > 0 else 'wb'
with open(local_path, mode) as f:
for chunk in response.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)
return True
踩坑记录:我曾经遇到一个问题,设备本地文件损坏了,但大小是对的。断点续传时服务器返回 206,但实际数据对不上,导致更新包校验失败。解决方案:每次续传完成后,必须做全文件 SHA256 校验。校验失败就删掉重下。
3.4.3 P2P 场景下的断点续传
P2P 场景更复杂。设备可能从多个节点下载不同分片,任何一个节点断线都要能切换到其他节点。
我建议的做法:
- 分片索引:每个设备维护一个分片位图(bitmap),记录哪些分片已下载
- 节点切换:如果当前节点下载超时(比如 30 秒无响应),立即切换到其他节点
- 本地缓存:已下载的分片写入本地临时目录,下次启动时加载位图,继续下载
位图示例:
# 假设文件分 10 片,已下载第 1、3、5 片
bitmap = [1, 0, 1, 0, 1, 0, 0, 0, 0, 0]
# 下次启动时,跳过已下载的分片
for i, downloaded in enumerate(bitmap):
if not downloaded:
download_piece(i, from_peers)
3.5 架构选型决策流程
最后,我画了一张决策流程图,帮你快速判断该用哪种架构。
决策逻辑很简单:
- 设备少(≤1000):直接 C/S,省心省力
- 设备多 + 内网:P2P,带宽省 80%
- 设备多 + 公网 + 大包:C/S + CDN,用 CDN 扛流量
- 设备多 + 公网 + 小包:C/S 也够用,做好限速就行
最后说一句:架构没有银弹。我见过用 C/S 管理 5000 台设备的,也见过用 P2P 管理 200 台设备的。关键看你的网络环境、设备能力和运维团队的水平。选你最能 hold 住的方案,比选最时髦的方案重要得多。