低带宽下的 S3 存储迁移:分片并发的实践与权衡
雅洁 发布于
一次 5GB 数据的跨服务器迁移,在源端带宽仅 5Mbps 的限制下,通过分片并发将传输效率推到带宽上限。
一、背景与约束
我维护了一个自托管的 RustFS 服务(兼容 S3 API / MinIO),主要用于存储学习教育相关的音频文件(单词/例句 MP3,约 5GB),支撑着一个小型在线教育应用。
由于旧服务器即将下线,需要将全部数据迁移到新服务器。但面临两个硬约束:
| 约束 | 说明 |
|---|---|
| 源端带宽 | 旧服务器出带宽仅 5Mbps(某厂商轻量应用服务器入门型) |
| 目标端状态 | 新服务器为全新实例,未安装任何存储服务软件 |
如果使用最直接的 scp 单连接传输,理论极限速度约 625KB/s,3.5GB 压缩包需要 1.5~2 小时。在真实的公网环境下,TCP 拥塞控制、丢包重传等因素会进一步拖慢速度,且一旦 SSH 断开,传输即中断,需要从头再来。
这意味着:需要一种能跑满 5Mbps 带宽、且具备中断可恢复能力的传输方案。
二、方案选型
2.1 备选方案
| 方案 | 原理 | 可行性 | 不选的原因 |
|---|---|---|---|
scp 直传 |
单连接 SSH 传输 | ❌ | 无法充分利用带宽,无断点续传 |
rsync |
增量同步 + SSH 通道 | ❌ | 小文件场景下校验开销大;目标端无服务,无法使用 daemon 模式,ssh 模式下单连接瓶颈依旧存在 |
rclone 多线程 |
多连接并发,支持断点续传 | ⚠️ | 需要目标端先有一个运行中的 S3 服务,形成"鸡生蛋"问题 |
mscp / para |
封装好的多线程 SCP 工具 | ❌ | 实测官方下载链接已失效(404),无法快速部署 |
| split + 并发 scp | 手动分片,多连接并行传输 | ✅ | 无额外依赖,仅需 SSH;可控性强,可观测性好 |
2.2 选择理由
split + 并发 scp 的核心思路是:将大文件切分为多个分片,每个分片通过独立的 SSH 连接并行传输。
这样做有两个收益:
- 多连接并发:通过建立多个 TCP 连接,减小单连接受 BDP(带宽延迟积)限制的影响,将带宽利用率从约 60% 提升到 95% 以上;
- 可观测性:每个分片独立传输,可以清晰看到各分片的进度,方便排查慢连接。
三、关键设计决策
3.1 为什么先打包再分片?
原始数据是 5GB 的目录树,包含数千个 MP3 小文件。如果直接分片目录,会产生两个问题:
- 每个小文件都需要建立 SSH 连接,握手开销远大于传输本身;
- 文件分配不均衡,无法控制每个分片的大小。
因此选择 先 tar 打包成单文件,再 split 切分,将小文件的元数据开销摊销到一次打包中。
3.2 分片大小为什么选 500MB?
这是一个需要权衡的参数:
| 分片大小 | 并发度 | 优势 | 劣势 |
|---|---|---|---|
| 太小(如 100MB) | 35 个分片 | 并发度高 | SCP 握手次数多;后台管理 35 个进程,资源开销大 |
| 适中(500MB) | 7 个分片 | 并发度足够填满 5Mbps;进程数可控 | 单个分片传输失败重传代价尚可接受 |
| 太大(1GB) | 4 个分片 | 管理简单 | 并发度不足,无法充分利用带宽 |
500MB 是在并发度和管理成本之间的一个平衡点。 7 个并发连接足以将 5Mbps 带宽跑满,同时进程数在 ps 和 jobs 的视野内清晰可查。
3.3 为什么用 SCP 而不是 HTTPS?
- 新服务器预装了 SSH 服务,无需额外部署;
- 通过
ssh-copy-id配置免密登录后,所有scp命令自动复用同一个密钥,无需处理 Token 过期或签名计算。
四、实现与踩坑
4.1 打包
tar -czf /tmp/rustfs_data_backup.tar.gz -C /www/wwwroot/rustfs data
打包后大小:3.5GB(MP3 本身已压缩,gzip 压缩率有限)。
4.2 切分与并发传输
# 切分为每个 500MB 的分片
split -b 500M -d --suffix-length=2 rustfs_data_backup.tar.gz rustfs_part_
split 命令各参数含义:
- -b 500M:每个分片大小为 500MB;
- -d:使用数字后缀(00、01…),而非字母后缀;
- --suffix-length=2:后缀长度为 2 位,支持最多 99 个分片;
- rustfspart:分片文件的前缀。
执行后生成分片文件
# 配置免密登录(避免多次密码输入)
ssh-keygen -t rsa -b 4096 -N "" -f ~/.ssh/id_rsa
ssh-copy-id root@目标服务器ip
验证免密登录是否生效:
ssh root@目标服务器ip "echo 'OK'"
# 并发传输
for part in rustfs_part_*; do
scp "$part" root@目标服务器ip:/tmp/ &
done
wait
这段脚本的执行逻辑:
- for part in rustfspart*:遍历所有分片文件;
- scp ... &:每个分片通过 & 放入后台执行,不阻塞下一个分片的启动;
- wait:等待所有后台 scp 进程完成,再继续后续操作。
实际传输效果:多个分片同时传输,单分片速度约 80~90KB/s,总带宽稳定在 620KB/s 左右,接近 5Mbps 的理论上限。
4.3 目标端合并
# 合并分片
cat rustfs_part_* > rustfs_data_backup.tar.gz
# 验证合并结果
ls -lh rustfs_data_backup.tar.gz
tar -tzf rustfs_data_backup.tar.gz | head -20
踩坑记录:在执行合并操作时遇到 No space left on device 报错。检查磁盘后发现根分区 / 已满,而 /www 数据分区还有充足空间。解决方案是将分片移动到 /www/temp 目录后再合并,避免写入系统分区。
4.4 解压与启动
# 创建数据目录
mkdir -p /www/wwwroot/rustfs
# 解压数据
tar -xzvf rustfs_data_backup.tar.gz -C /www/wwwroot/rustfs
# 启动 RustFS 容器
docker run -d \
--name rustfs \
--restart always \
-p 9000:9000 \
-p 9001:9001 \
-e RUSTFS_ROOT_USER='<用户名>' \
-e RUSTFS_ROOT_PASSWORD='<你的密码>' \
-e MINIO_SERVER_URL='http://<你的存储域名>' \
-e MINIO_BROWSER_REDIRECT_URL='http://<你的控制台域名>' \
-v /<你的宿主机路径>/data:/data \
-v /<你的宿主机路径>/config:/config \
rustfs/rustfs
验证容器运行状态:
docker ps | grep rustfs
docker logs rustfs --tail 20
五、延伸思考:方案的局限性
这套方案适用于:
- 数据量在 10GB 以内
- 源端带宽在 50Mbps 以下
- 目标端无需预先部署任何服务
但如果数据量增长到 500GB,这套方案将不再适用。更合适的选择包括:
- 使用 rclone 的多线程上传到对象存储中转(如 AWS S3 / 腾讯云 COS);
- 使用专业工具如 bbcp(专为高速网络设计)或 fpart(并行文件传输);
- 若两台服务器同属一个云厂商,可使用内网高速通道 + 快照迁移。
六、命令速查
# 打包
tar -czf backup.tar.gz -C /source data
# 验证压缩包
tar -tzf backup.tar.gz | head -20
# 分片
split -b 500M -d --suffix-length=2 backup.tar.gz part_
# 免密登录
ssh-keygen -t rsa -b 4096
ssh-copy-id root@target_ip
# 并发传输
for f in part_*; do scp "$f" root@target:/path/ & done; wait
# 合并
cat part_* > backup.tar.gz
# 查看磁盘空间
df -h
📌 本文所有操作在 CentOS 7/8 环境下验证通过,RustFS 版本为 v1.0+。