用net/rpc传文件必踩坑,高并发下不是oom就是连接雪崩;必须切到grpc+流式rpc,并手动控制分块、校验和超时——否则压测一过就挂。

直接结论:用 net/rpc 传文件必踩坑,高并发下不是 OOM 就是连接雪崩;必须切到 gRPC + 流式 RPC,并手动控制分块、校验和超时——否则压测一过就挂。
为什么 net/rpc 不适合传文件
标准库 net/rpc 是 unary 模型,整个请求体必须一次性加载进内存再解码。哪怕你客户端“分块发”,服务端也得等流结束才开始处理,中间数据全堆在 buffer 里。io.EOF 不代表传输完成,只代表连接断了;没做任何流控、校验、重试机制,网络抖动一次就丢半份文件。
- 默认使用
Gob编码,不支持压缩,大结构体体积膨胀 2–3 倍 - 无连接复用,每个文件请求新建 TCP 连接,QPS 上千时
TIME_WAIT爆满 - 无法感知 chunk 边界,
Read()返回的[]byte可能跨逻辑块,解析错位风险高
gRPC 客户端流式上传怎么写才不崩
关键不是“能不能传”,而是“怎么定义接口 + 怎么发包 + 怎么收 err”。写错 stream 关键字位置,生成的 Go 方法就完全不可用。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 必须定义为
rpc Upload(stream Chunk) returns (UploadResult)(客户端流),不是rpc Upload(Chunk) returns (stream UploadResult)(服务端流) -
Chunk消息只含两个字段:bytes data和uint64 offset,别塞文件名、MD5、时间戳进去——元数据走首块或单独 RPC - 每次
Send()后必须检查err:if err != nil { return err };遇到io.EOF才退出循环,否则下一次Send()会 panic - 用
bufio.NewReader(file).Read(buf)分块读,buf推荐make([]byte, 256*1024)(256KB),太小 syscall 过多,太大易触MaxCallRecvMsgSize限制
服务端接收流时最常漏掉的三件事
Recv 循环看着简单,但生产环境出问题基本都卡在这几步上。
- 不用
for { stream.RecvMsg(&chunk) },要用带超时的stream.RecvMsg(&chunk)配合context.WithTimeout(ctx, 30*time.Second)——否则某次网络卡顿,goroutine 就永远挂起 - 每个
Chunk必须带单调递增seq字段,服务端用map[uint64]*pb.Chunk缓存乱序块;收到seq == 0才初始化 session,seq == math.MaxUint64标记结束 - 落盘不能每块
Write()后立刻fsync(),应攒够 1MB 或收到结束标记再刷盘;校验用sha256.Sum256对最终文件整体计算,失败返回status.Error(codes.Aborted, "checksum mismatch")
压缩、限流、监控怎么配才真实可用
gzip 不是开个开关就行,它和 chunk size、CPU 调度强耦合;限流也不是套个 token bucket 就完事。
- 启用压缩要配两处:
grpc.WithCompressor(gzip.NewCompressor())(客户端) +grpc.WithDecompressor(gzip.NewDecompressor())(服务端),缺一不可 - 压缩阈值建议设为 8KB:小于该值跳过压缩,避免小块压缩反而变大;大于则启用,实测内网压缩比稳定在 3.2:1 左右
- 限流不能只限 QPS,要按文件大小加权:上传 100MB 文件计为 100 个单位,1KB 日志计为 1 个,用
golang.org/x/time/rate.Limiter控制加权速率 - 必须暴露
/debug/pprof/goroutine?debug=2和自定义指标如grpc_upload_chunks_total{status="ok"},否则流卡死时连 goroutine 堆栈都抓不到
真正难的不是写通流式 RPC,而是让每一块数据可追溯、可中断、可重试、可对账。chunk 的 offset 和 seq 要对得上,服务端缓存不能无限增长,客户端重试要带断点偏移——这些细节没对齐,传 100 个文件可能成功 99 个,剩下一个永远差 3 字节。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










