分块并行上传与断点续传:AWS S3/阿里云OSS大文件上传

当您尝试上传数百MB甚至GB级别的大文件时,网络波动、连接超时、服务器限流等问题会频繁导致上传失败。分块上传与断点续传正是解决这一痛点的成熟方案。本文以AWS S3和阿里云OSS为例,详解分块上传的工作原理、SDK调用步骤、并行并发控制及断点续传的实现细节,助您构建可靠的大文件上传服务。

分块并行上传与断点续传:AWS S3/阿里云OSS大文件上传
封面图:ZuCDN · ZuCDN 原创

为什么必须用分块上传?

我的处理经验

关于分块上传,最值得先弄清楚的是配置边界和排错顺序。上传超过 100 MB 的文件时,传统的单一 PUT 请求面临三个致命问题:

  • 网络抖动导致全量重传:一旦连接中断,整个文件就需要重新上传。
  • 超时限制:大多数负载均衡器和 CDN 都有 30~60 秒的连接空闲超时,大文件很可能在传输中途被截断。
  • 内存开销:客户端需要将整个文件读入内存,对于 2 GB 的文件,内存占用直接超过 2 GB。

分块上传(Multipart Upload)将文件切分为若干个较小的部分(通常 5 MB ~ 1 GB),每个分块独立发起 PUT 请求,最后服务端自动合并。这天然支持并行上传断点续传——失败时只需重传未完成的分块,效率提升显著。

AWS S3 分块上传实现

1. 初始化与分块策略

S3 的分块上传分为三个阶段:CreateMultipartUploadUploadPartCompleteMultipartUpload。分块大小建议为 8 MB ~ 100 MB,总块数不超过 10000。

import boto3
from boto3.s3.transfer import TransferConfig

s3_client = boto3.client('s3')
response = s3_client.create_multipart_upload(
    Bucket='my-bucket',
    Key='large-file.zip'
)
upload_id = response['UploadId']

2. 并行上传与 ETag 捕获

每个分块上传后,S3 返回 ETag(MD5 哈希)。必须按分块编号保存这些 ETag,用于最后的合并请求。使用 Python 的 concurrent.futures 或 asyncio 实现并行。

import concurrent.futures

def upload_part(file_path, part_number, upload_id):
    with open(file_path, 'rb') as f:
        f.seek((part_number - 1) * part_size)
        data = f.read(part_size)
        response = s3_client.upload_part(
            Bucket='my-bucket',
            Key='large-file.zip',
            PartNumber=part_number,
            UploadId=upload_id,
            Body=data
        )
        return {'PartNumber': part_number, 'ETag': response['ETag']}

with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
    futures = [executor.submit(upload_part, file_path, i, upload_id) for i in range(1, total_parts+1)]
    parts = [f.result() for f in concurrent.futures.as_completed(futures)]

3. 完成合并

s3_client.complete_multipart_upload(
    Bucket='my-bucket',
    Key='large-file.zip',
    UploadId=upload_id,
    MultipartUpload={'Parts': parts}
)

4. 断点续传实现

在上传过程中将 upload_id 和已完成的 parts 列表持久化到本地数据库或 Redis。如果程序中断,重新调用 list_parts 获取已上传的分块列表,只上传缺失的分块即可。注意:如果调用 abort_multipart_upload 会清除所有分块,需谨慎使用。

# 恢复时查询已有分块
resp = s3_client.list_parts(Bucket='my-bucket', Key='large-file.zip', UploadId=upload_id)
uploaded_parts = {p['PartNumber']: p['ETag'] for p in resp.get('Parts', [])}
# 仅上传 missing_parts

阿里云 OSS 分块上传实现

1. 初始化与分块策略

OSS 的接口称为 InitiateMultipartUploadUploadPartCompleteMultipartUpload,语义与 S3 高度一致。官方 Python SDK(oss2)提供了更简洁的高层封装:

import oss2

auth = oss2.Auth('AccessKeyId', 'AccessKeySecret')
bucket = oss2.Bucket(auth, 'https://oss-cn-hangzhou.aliyuncs.com', 'my-bucket')

# 使用 oss2.resumable_upload 自动支持分块 + 断点续传
oss2.resumable_upload(bucket, 'large-file.zip', 'local-large-file.zip',
                      multipart_threshold=10*1024*1024, part_size=5*1024*1024,
                      num_threads=8)

resumable_upload 会在本地生成一个 checkpoint 文件(默认在 ~/.oss_upload/),记录已上传的分块。如果上传中断,下次调用同一文件会自动从断点继续。

2. 手动控制并发与分块校验

如果需要精细控制,可以像 S3 一样手动调用底层 API。OSS 每个分块上传后同样返回 ETag,但 ETag 是分块内容的 MD5 值(不含引号)。注意:OSS 要求分块大小必须大于 100 KB(最后一个分块除外),且除最后一块外最小为 100 KB。

upload_id = bucket.init_multipart_upload(key).upload_id
parts = []
for i in range(1, total_parts+1):
    result = bucket.upload_part(key, upload_id, i, data)
    parts.append(oss2.models.PartInfo(i, result.etag))
bucket.complete_multipart_upload(key, upload_id, parts)

关键参数与最佳实践

分块大小

分块越小,重传粒度越细,但分块数量增多会增加服务端合并的开销。推荐:

  • 100 MB ~ 1 GB 文件:分块 8 MB
  • 1 GB ~ 10 GB 文件:分块 16~32 MB
  • 10 GB 以上:分块 64~100 MB(注意 S3 总块数不超过 10000)

并发数

并行上传分块能充分利用带宽,但过高的并发反而会导致 TCP 拥塞和 CPU 争抢。经验值:

  • 客户端带宽 100 Mbps:并发 4~8
  • 客户端带宽 500 Mbps:并发 8~16
  • 服务器端限流时:逐级降低并发,观察 429 错误

超时与重试

每个分块的 HTTP 请求应设置合理的超时(如 60 秒)。捕获 socket.timeout 或 SDK 的 RequestTimeout 异常后,自动重试该分块(最多 3 次)。避免因一个分块失败导致整个上传任务放弃。

MD5 校验

部分云存储要求上传时提供分块的 Content-MD5。S3 的 UploadPart 会自动计算并校验;OSS 需要显式设置 Content-MD5 头。如果检测到分块损坏,立即重传。

常见问题与排查

错误码 400 EntityTooSmall

S3 要求除最后一个分块外,每个分块大小至少 5 MB。如果手动切分时使用了更小的尺寸,会收到该错误。解决方案:将小于 5 MB 的块合并到上一块或最后一块。

错误码 403 AccessDenied

分块上传涉及多个 API,需要 IAM 策略允许 s3:PutObjects3:AbortMultipartUploads3:ListMultipartUploadParts 等操作。OSS 则需要 oss:PutObjectoss:InitiateMultipartUpload 权限。

上传速度不理想

检查客户端与存储节点之间的网络延迟和丢包率。使用 CDN 加速上传或选择离用户最近的区域(如 AWS 的 S3 Transfer Acceleration 或 OSS 的传输加速)。

总结

先看关键判断

分块上传不是锦上添花的优化,而是大文件传输的基础设施。无论是 AWS S3 还是阿里云 OSS,其 API 设计都支持从初始化到完成的完整流程,并且均提供了 SDK 级别的断点续传封装。在实际工程中,建议直接使用 SDK 自带的 resumable 函数(如 oss2.resumable_upload 或 boto3 的 TransferConfig 的 use_threads=True),它们已经处理了大部分边界情况。如果仍需定制,请务必关注分块大小、并发数、超时重试和 ETag 校验这四个核心参数。掌握这些,您的大文件上传功能将像本地拷贝一样可靠。

延伸阅读