3、更新架构设计:C/S架构 vs P2P架构、更新服务器部署、带宽优化策略、断点续传机制

更新架构选型,说白了就是回答一个问题:更新包怎么从服务器跑到设备上?

我见过太多团队,上来就拍脑袋选C/S,结果几千台设备同时拉包,带宽直接被打满。也见过迷信P2P的,结果内网设备互相找不到,更新比蜗牛还慢。

这一章,我把两种架构的实战经验、部署要点、带宽优化和断点续传机制,一次性讲透。

3.1 C/S架构 vs P2P架构:怎么选?

先看一张对比图,帮你快速建立认知。

C/S架构 vs P2P架构 核心对比 C/S 架构(中心化) 更新服务器(单点/集群) 设备1 拉取更新包 设备2 拉取更新包 设备N 拉取更新包 所有设备都从服务器下载 服务器带宽压力大 P2P 架构(去中心化) 种子服务器(仅元数据) 设备A 设备B 设备C 设备D 设备之间互相分享数据块 服务器带宽压力小

两种架构各有适用场景。我直接给你一个决策表,照着选就行。

对比维度 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 轮询或负载均衡器分发。

架构图如下:

更新服务器高可用部署 负载均衡器(HAProxy/Nginx) 更新服务器 1(主) 更新服务器 2(备) 共享存储(NFS/Ceph/MinIO)

关键点:

  • 共享存储:更新包放在 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 架构 更新包 > 500MB? 是 否 C/S + CDN C/S 架构

决策逻辑很简单:

  • 设备少(≤1000):直接 C/S,省心省力
  • 设备多 + 内网:P2P,带宽省 80%
  • 设备多 + 公网 + 大包:C/S + CDN,用 CDN 扛流量
  • 设备多 + 公网 + 小包:C/S 也够用,做好限速就行

最后说一句:架构没有银弹。我见过用 C/S 管理 5000 台设备的,也见过用 P2P 管理 200 台设备的。关键看你的网络环境、设备能力和运维团队的水平。选你最能 hold 住的方案,比选最时髦的方案重要得多。