并发上传大文件需流式读取分片复用缓冲区,用io.ReadAt/Seek配4MB缓冲区;用semaphore限并发数(3–5)并配合context超时;分片附带part_number等元数据;仅全成功后调用幂等merge API。

并发上传时如何避免内存爆掉
直接读整个大文件进内存再分片,Go 程序很快会 panic: runtime out of memory。必须流式读取 + 分片复用缓冲区。核心是用 io.ReadAt 或 os.File.Seek 配合固定大小的 []byte 缓冲区(比如 4MB),每次只加载一个分片内容到内存。
- 不要用
os.ReadFile或bufio.Scanner处理 GB 级文件 - 每个 goroutine 自己打开文件并
Seek到对应 offset,避免文件句柄竞争 - 缓冲区大小建议设为 222(4MB)——太小导致 syscall 过多,太大挤占 heap
如何安全控制并发数和失败重试
无限制启 goroutine 会导致系统 fd 耗尽或服务端限流拒绝,必须硬性限流。用 semaphore(信号量)比 channel 更直观可控,且能配合 context 实现超时退出。
- 用
golang.org/x/sync/semaphore初始化带权重的信号量,权重设为 1,容量即最大并发数(如 3–5) - 每个分片上传前
sem.Acquire(ctx, 1),成功或失败后必须sem.Release(1) - 单个分片失败时重试 2 次(含首次),超过则标记该分片失败,不阻塞其他分片
- 整个上传任务设总 timeout(如 30 分钟),用
context.WithTimeout包裹所有 goroutine
分片元信息怎么组织才方便服务端合并
服务端需要知道每个分片的原始顺序、总片数、文件名和校验值,否则无法正确拼接。客户端必须在每个分片上传时附带结构化元数据,不能靠文件名解析。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 上传 body 用
multipart/form-data,至少包含字段:file_part(二进制)、part_number(int)、total_parts(int)、file_name(string)、md5(hex string) -
part_number从 0 开始或从 1 开始需和服务端约定,推荐从 1 开始(更符合直觉) - 整个文件的 MD5 应在上传前一次性计算(用
crypto/md5+io.Copy流式算),而非拼接后二次计算
上传完成后如何可靠触发合并请求
所有分片成功 ≠ 合并成功。必须显式调用 merge API,并校验响应状态码和 body 中的 success 字段,否则可能留下孤儿分片。
立即学习“go语言免费学习笔记(深入)”;
- 仅当
len(failedParts) == 0才发起 merge 请求;否则直接返回错误,带上失败的part_number列表 - merge 请求必须带相同
file_name和total_parts,服务端靠这两个字段定位待合并分片 - merge 接口超时应比单个分片长(如 5 分钟),且需幂等:重复调用应返回已合并结果,而非报错
真正麻烦的是分片上传的边界情况:网络抖动导致部分请求发出但没收到响应、服务端写磁盘失败却返回 200、断点续传时本地记录丢失……这些没法靠一个函数兜底,得靠外部持久化任务状态 + 定期 reconcile。

















