
本文介绍使用 `juju/ratelimit` 库结合令牌桶算法,为 go 编写的 http 文件服务(上传/下载)添加可配置的带宽限速功能,支持精确控制如 1mb/s 的读写速率。
在构建高可用文件传输服务时,不限速的 I/O 可能导致带宽打满、响应延迟升高甚至影响其他请求。Go 标准库本身不提供内置速率限制器,但借助成熟的第三方限速库(如 juju/ratelimit),我们可通过令牌桶(Token Bucket) 算法优雅实现平滑、可控的上传/下载限速。
该算法核心思想是:以恒定速率向“桶”中注入令牌,每次读/写操作需消耗对应字节数的令牌;若令牌不足则阻塞等待,从而自然达成平均速率上限。ratelimit.Bucket 支持毫秒级精度,且线程安全,非常适合 HTTP 并发场景。
✅ 下载限速(服务端响应流)
对 http.ResponseWriter 的写入进行限速,需包装 http.ResponseWriter 的底层 io.Writer。推荐方式是创建一个限速的 io.Writer 包装器:
func downloadFile(w http.ResponseWriter, r *http.Request) {
f, err := os.Open(`e:\test\test.mpg`)
if err != nil {
http.Error(w, "file not found", http.StatusNotFound)
return
}
defer f.Close()
// 限速:1 MB/s = 1_048_576 bytes/sec
bucket := ratelimit.NewBucketWithRate(1_048_576, 1_048_576)
// 设置响应头
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", `attachment; filename="test.mpg"`)
// 使用限速 Writer 包装 ResponseWriter 的 Write 方法
limitedWriter := ratelimit.Writer(w, bucket)
_, err = io.Copy(limitedWriter, f)
if err != nil && err != io.ErrClosedPipe {
log.Printf("download error: %v", err)
}
}⚠️ 注意:io.ErrClosedPipe 是客户端主动断连的常见错误,建议忽略以避免日志噪音。
✅ 上传限速(服务端请求体读取)
对 http.Request.Body 或 multipart.File 的读取限速,只需用 ratelimit.Reader 包装原始 io.Reader:
func uploadFile(w http.ResponseWriter, r *http.Request) {
r.ParseMultipartForm(32 << 20) // 32MB 内存缓冲
file, _, err := r.FormFile("file")
if err != nil {
http.Error(w, "invalid file field", http.StatusBadRequest)
return
}
defer file.Close()
os.MkdirAll(`e:\test`, 0755)
out, err := os.Create(`e:\test\test.mpg`)
if err != nil {
http.Error(w, "failed to create file", http.StatusInternalServerError)
return
}
defer out.Close()
// 限速:1 MB/s(可替换为配置项)
bucket := ratelimit.NewBucketWithRate(1_048_576, 1_048_576)
limitedReader := ratelimit.Reader(file, bucket)
_, err = io.Copy(out, limitedReader)
if err != nil {
http.Error(w, "upload failed: "+err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("upload success"))
}? 配置化与最佳实践
- 动态速率:将 1_048_576 替换为从配置(如 flag.IntVar 或 viper)读取的变量,实现运行时灵活调整。
- 多用户隔离:若需按用户/IP 限速,应为每个会话/连接创建独立 Bucket(例如基于 r.RemoteAddr 做 map 缓存 + TTL 清理),避免全局速率被单个大文件霸占。
- 内存友好:NewBucketWithRate 的 capacity 参数(第二参数)建议设为单次最大读写量(如 64KB–1MB),过大会增加内存占用,过小可能导致突发流量抖动。
- 监控集成:bucket.Available() 可实时获取剩余令牌数,配合 Prometheus 暴露 rate_limit_remaining_tokens 指标,便于运维观测。
通过上述方式,你无需修改业务逻辑主干,仅需两行包装代码即可为任意 io.Reader/io.Writer 添加精准、低开销的速率控制——让大文件传输更可控、更公平、更健壮。










