华为云OBS大文件上传调优实战
华为云 OBS 大文件上传调优:为什么并发从 8 调到 32 反而更慢
一个 20 GB 的备份文件往 OBS 传,默认参数要 3 小时。直觉是"加并发",于是把并发从 5 一路拉到 32——结果速度下降了一半,重试率飙到 7%。这篇文章把分段上传的参数、带宽积、重试风暴串起来,给出一套可以照着做阶梯压测的方法。
一、先把问题量化
压测对象:单文件 20 GB,从本地机房经公网传到 OBS,客户端 8 核 16 GB,出口带宽 1 Gbps(理论峰值 125 MB/s)。
第一轮把并发数当唯一变量,阶梯式拉高:
| 并发数 | 平均吞吐 | 重试率 | 客户端 CPU |
|---|---|---|---|
| 1 | 18 MB/s | 0% | 15% |
| 4 | 62 MB/s | 0.2% | 35% |
| 8 | 96 MB/s | 0.5% | 62% |
| 16 | 104 MB/s | 1.8% | 88% |
| 32 | 71 MB/s | 7.4% | 97% |
| 64 | 43 MB/s | 21% | 99% |
曲线是标准的"先升后降":平台期在 8~16 之间,超过 16 之后吞吐掉头向下,同时重试率从 0.5% 涨到 7.4%。
二、为什么加并发会变慢
三个原因,任何一个先撞到天花板,继续加并发都只会制造重试:
① 出口带宽已经饱和。 1 Gbps 网卡的实际上限约 115~120 MB/s,实测 104 MB/s 已经是 90% 利用率。此时再开更多连接,不会带来更多带宽,只会让每个连接分到的带宽变少、单分片传输时间变长,进而更容易触发超时。
② 重试风暴是负反馈。 超时的分片会重传,重传又占用本已饱和的带宽,导致更多分片超时。重试率从 0.5% 到 7.4% 不是线性恶化,而是跨过临界点后的雪崩。
③ 客户端 CPU 成为次生瓶颈。 并发 32 时客户端 CPU 到 97%,分片校验(CRC/MD5 计算)和内存拷贝抢不到 CPU,进一步拉长单分片耗时。
结论:并发数不是"越大越好"的旋钮,而是有明确平台期的参数,且平台期位置由最窄的那一段决定——带宽、CPU、还是对端限速。
三、分片大小:先算约束,再谈调优
分段上传有两个硬约束(与 S3 协议一致):
- 单个分片 100 KB ~ 5 GB
- 分片总数 不超过 10000
由此得到分片大小的下限公式:
partSize ≥ 文件大小 / 10000
20 GB 的文件,分片至少要 2 MB 才能装下。但"能装下"离"跑得快"还很远。
分片太小的代价:请求数爆炸、每次请求的签名/建连/TLS 开销占比上升、服务端小请求处理效率低。
分片太大的代价:单个分片重传的成本剧增。一次分片失败就要重传整个分片——100 MB 的分片重传一次,等于白传 100 MB。
更靠谱的下限来自带宽时延积(BDP):
单连接吞吐 ≈ 窗口大小 / RTT
跨地域公网 RTT 通常在 20~50 ms,单连接很难跑满高带宽,必须靠多连接叠加。经验取值是让单分片的传输时间远大于 RTT(至少 10 倍以上),否则 TCP 慢启动还没结束,分片就传完了,连接利用率极低。
按 RTT 30 ms、单连接目标 15 MB/s 估算:
partSize ≥ 15 MB/s × 0.03 s × 10 ≈ 4.5 MB
实测下来,16~64 MB 是公网跨地域上传的甜点区间:20 GB 文件切成 32 MB 分片是 640 片,远低于 10000 的上限,单个分片重传的代价也可接受。
四、可落地的配置
1. 用 obsutil 批量上传
obsutil 的并发与分片相关配置项(键名以 obsutil help 的实际输出为准,不同版本略有差异):
# 查看当前配置
./obsutil help
# 关键配置项大致对应:
# defaultBigfileThreshold 超过该值才走分段上传
# defaultPartSize 分片大小(MB)
# defaultParallels 并发数
调完再跑,务必带断点续传相关参数,失败后只补缺口,不重传全量:
./obsutil cp /data/backup.tar.gz obs://my-bucket/backup/ -f -u
2. 用 SDK 时显式指定分片与并发
from obs import ObsClient
client = ObsClient(
access_key_id=AK,
secret_access_key=SK,
server="https://obs.cn-north-4.myhuaweicloud.com",
)
resp = client.putFile(
bucketName="my-bucket",
objectKey="backup/backup.tar.gz",
file_path="/data/backup.tar.gz",
partSize=32 * 1024 * 1024, # 32 MB
taskNum=8, # 并发 8,取平台期的 70%
)
注意 partSize 和 taskNum 的乘积决定了内存水位:32 MB × 8 = 256 MB 的缓冲区,在内存吃紧的机器上要一起算进去。
3. 并发数按阶梯压测取平台期的 70%
不要拍脑袋定并发。方法很简单:从 4 开始翻倍,每次跑 2~3 分钟,记录吞吐和重试率,在吞吐首次不再显著上升(涨幅 < 10%)时停止,然后回退到该值的 70%。回退这一步是为了给突发重试留出余量——压测环境干净,线上不一定。
4. 多文件并发要统一限流
最容易踩的坑:为了传 100 个小文件,起 100 个线程,每个线程内部又开 8 个分片并发 = 800 并发,直接把网卡和 CPU 打爆。
正确做法:全局限流。 用一个信号量控制整个进程的总并发,而不是每个文件各自开池。
from concurrent.futures import ThreadPoolExecutor
GLOBAL_PARALLEL = 8 # 全局总并发,不是每文件
with ThreadPoolExecutor(max_workers=GLOBAL_PARALLEL) as pool:
futures = [pool.submit(upload_one, f) for f in files]
五、一个容易被忽略的存储成本坑:未完成的分段上传
分段上传如果中途失败或被中断,已经上传的分片会留在桶里并持续计费,但在控制台的"对象列表"里看不到——因为它们还不是一个完整对象。
上传失败的次数多了,这部分"碎片"会悄悄累积。解决办法是配一条生命周期规则,自动清理未完成的分段上传:
规则类型:清理未完成的分段上传
生效范围:整个桶(或指定前缀)
过期天数:7 天
配好之后,超过 7 天仍未完成的分段上传会被自动回收。这条规则建议所有用分段上传的桶都加上。
六、验证口径
调优前后必须用同一套口径,否则数据没意义:
| 指标 | 含义 | 采集方式 |
|---|---|---|
| 平均吞吐 | 总字节 / 总耗时 | 上传工具输出 |
| 重试率 | 重试分片数 / 总分片数 | 客户端日志统计 |
| 分片耗时 P99 | 单分片从发起到完成 | 客户端埋点 |
| 客户端 CPU / 内存峰值 | 判断是否成为瓶颈 | 系统监控 |
重点看分片耗时 P99,而不是平均值。 平均值好看但 P99 很高,说明已经在临界点附近,线上流量一抖就会雪崩。
七、小结
四句话:
- 并发数有平台期,位置由最窄的一段决定(带宽 / CPU / 对端限速),超过平台期加并发只会制造重试风暴。
- 分片大小先算下限再选甜点:
文件大小 / 10000是硬下限,BDP 估算给出可用下限,公网跨地域场景 16~64 MB 比较稳。 - 多文件场景必须全局限流,别让每个文件各自开线程池,否则并发数是乘法不是加法。
- 给桶配一条清理未完成分段上传的生命周期规则,否则失败的分片会一直计费。
如果只记一句:上传慢的时候,先看出口带宽和 CPU 是不是已经满了,再决定要不要加并发。
建议标签:对象存储 OBS、云存储、性能优化、数据传输
- 点赞
- 收藏
- 关注作者
评论(0)