rate.limiter 必须作用于每次读取操作前(如 resp.body.read 或 conn.read),而非 handler 入口或连接建立后;需包装 io.readcloser 或 responsewriter 的 write 方法,确保令牌按实际读写字节数扣减,且每个 goroutine 使用独立 limiter 实例。

rate.Limiter 必须作用在 Read 调用路径上,而不是连接建立后
限速不是设置 http.Transport.ReadBufferSize 或调大 context.WithTimeout,这些只影响缓冲或超时,不控制字节吞吐。真正生效的位置是:每次从底层 net.Conn 或 http.Response.Body 读取数据前,必须显式扣减令牌。
常见错误是把限速逻辑放在 handler 入口——那只能限制请求频率,对单个大文件下载或长连接流式响应完全无效。真正的瓶颈在 io.Copy、resp.Body.Read 或自定义 io.Reader.Read 实现里。
- HTTP 下载场景:限速对象是
resp.Body,不是http.Client - TCP 代理/转发场景:限速对象是
conn.Read,不是listener.Accept - 必须确保每个 goroutine 持有独立的
rate.Limiter实例,共享 limiter 会导致速率不均甚至卡死
别用 io.LimitReader,它只截断不控速
io.LimitReader(resp.Body, 1024*1024) 看似限速,实则是硬截断:读满 1MB 就返回 io.EOF,后续 resp.Body.Close() 可能漏调,连接泄漏;服务端以为传输完成,客户端却收到损坏文件。
限速的本质是「单位时间放行多少字节」,不是「最多允许读多少字节」。正确做法是包装 io.ReadCloser,每次 Read(p []byte) 返回真实字节数 n 后,立刻调用 limiter.WaitN(ctx, n)。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- burst 参数必须 ≥ limit,例如
rate.NewLimiter(rate.Limit(512*1024), 512*1024)表示 512KB/s,burst 至少设为 512KB - 不能写
rate.Every(time.Second / 512000),Go 编译器可能优化掉除法,应显式用rate.Limit(512*1024) - 如果
ctx被 cancel,WaitN返回 error,此时必须立即停止读取并调用Close()
HTTP Server 响应体限速要包装 ResponseWriter,不是 Conn
直接封装 net.Conn 虽然可行,但在 HTTP 场景下容易出问题:gzip 压缩、chunked 编码、header 写入时机都会干扰限速节奏。更稳妥的方式是包装 http.ResponseWriter,重写 Write([]byte) 方法。
关键点是:限速必须发生在 WriteHeader 之后、首次 Write 开始之前;且要避开压缩中间件(如 gzip.Writer)的包裹顺序,否则限速作用在压缩前数据上,实际网络带宽不受控。
- 务必检查
ResponseWriter是否已实现http.Hijacker或http.Flusher,避免包装后接口丢失 - 若启用了
Content-Length,限速不能改变 body 长度,否则客户端校验失败;若动态限速(如按用户等级),需改用Transfer-Encoding: chunked - 写入缓冲区建议设为 1MB:
bufio.NewWriterSize(w, 1024*1024),避免高频小 write 拖慢限速节奏
上传限速必须配 io.Pipe + goroutine,否则 multipart 会乱套
把限速 io.Reader 直接当 http.Request.Body 传给 client 是危险的:multipart.Writer 会反复调用 Read,导致同一段数据被多次限速,速率翻倍甚至死锁。
根本原因是 HTTP client 内部做了 buffer 预读和重试,而限速 Reader 没有状态隔离。解决方案是引入 io.Pipe,启动 goroutine 单向搬运数据。
- 写端(goroutine):从限速 Reader 读、往
pipeWriter写;读端(client):从pipeReader读 -
pipeWriter.Close()必须由写端触发,否则读端永远阻塞;panic recover 后也要 close writer - 大文件(>100MB)不用管道,内存占用会飙升——因为 client 可能预读整个 body 到内存
os.File.Write 或 net.Conn.Write 不拖后腿。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










