正确做法是用 io.copy 合并分片:目标文件用 os.create,分片用 os.open 直接传入 io.copy,避免内存加载;需数字排序分片名,如 part_001、part_002,防止字典序错位。

用 io.Copy 合并分片,别碰 os.ReadFile 或 bytes.Buffer
GB 级文件合并失败、PDF 打不开、ZIP 解压报 CRC 错,90% 是因为用了 os.ReadFile、io.ReadAll 或 bytes.Buffer.Write —— 它们会把整块数据加载进内存,一卡就 OOM。正确做法是打开目标文件后,对每个分片调用 io.Copy(dst, src),只搬运字节流,内存占用恒定在几 KB。
注意三点:
- 目标文件必须用
os.Create(dstPath),不是os.OpenFile(..., os.O_APPEND)—— 后者首次创建时看似能写,但后续逻辑混乱,容易残留旧内容 - 每个分片用
os.Open(srcPath)直接传给io.Copy,中间不经过任何[]byte缓冲 - 缓冲区大小不用手动设:
io.Copy内部默认 32KB,对 NVMe/SSD 已足够;真要调优,才用io.CopyBuffer包一层
分片文件名必须数字排序,不能依赖字符串字典序
filepath.Glob("part_*") 返回 part_1、part_10、part_2 是正常现象,但按这顺序合并,数据必然错位。这不是 Go 的 bug,是所有字符串排序的通病。
必须显式提取数字再排序:
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 生成分片时强制零填充:
fmt.Sprintf("part_%03d", i)→part_001、part_002 - 读取前用
sort.Slice(files, func(i, j int) bool { return extractNum(files[i]) ,其中 <code>extractNum从文件名中提整数 - 若前缀复杂(如
backup_20240501_part_042),先用strings.Split或正则提取数字再转int - 别依赖时间戳或随机字符串排序 —— 它们无法表达逻辑先后
合并后必须校验 sha256.Sum256,不能只比文件大小
os.Stat().Size 一致 ≠ 内容正确。末尾几个字节丢失、中间某块被截断、I/O 中断未写满 —— 这些都可能导致大小不变但内容损坏,尤其 ZIP、视频、数据库 dump 文件会直接不可用。
校验必须严格:
- 分割前:用
sha256.New()+io.Copy流式算原始哈希,不加载全文 - 合并后:对新文件做同样哈希计算,对比两个
[32]byte值是否相等 - 别用
md5—— 碰撞风险高;也别跳过校验 —— “写对了却没校验”才是真麻烦 - 校验失败必须立刻删掉目标文件和所有分片 —— 留着脏数据比报错更危险
服务端合并时并发控制和打开模式不能松懈
多个请求并发写同一上传任务的分片,不加约束就会丢数据;临时分片不清理,几天就能占满磁盘。
关键约束点:
- 每个分片落地前,必须校验
X-Chunk-Hash或Content-MD5header,防止网络损坏或中间篡改 - 接收分片接口必须幂等:检查该分片是否已存在且大小/哈希一致,一致则直接返回成功,不重复写磁盘
- 合并操作本身应串行执行,避免多个合并请求同时操作同一
upload_id目录;可用sync.Mutex或 Redis 分布式锁控制 - 临时分片目录需定期清理(如 cron 每小时扫一次超 24 小时未完成的
upload_id)
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










