换fasthttp未必提升QPS,关键在正确使用:1. RequestCtx不可跨goroutine持有,需复制数据;2. 响应必须用ctx.Write*系列方法;3. Client需按host配置连接池;4. 热路径须避免堆分配。

直接换 fasthttp 不一定能提升 QPS,关键在「怎么用」——多数人栽在复用模型误用、数据生命周期错判、或盲目套用 net/http 习惯上。
fasthttp.RequestCtx 不能跨 goroutine 持有
这是最常踩的坑。标准库里 http.Request 是每个请求新建的,可安全传给子协程;而 fasthttp.RequestCtx 是复用对象,一旦离开 handler 函数作用域,内存可能已被下个请求覆盖。
- 错误写法:
go processLater(ctx.PostBody())——ctx.PostBody()返回的是内部切片指针,子协程读到的可能是其他请求的数据 - 正确做法:立即复制数据,再传入 goroutine:
body := append([]byte(nil), ctx.PostBody()...) - 同理,
ctx.QueryArgs()、ctx.FormValue()等返回值都需显式拷贝,不能直接保存引用 - 若需异步处理,建议用
ctx.UserValue()存放已拷贝的值,或改用 channel + worker pool 控制并发
响应写入必须用 ctx.Write* 系列方法
fasthttp 不提供 http.ResponseWriter 接口,所有响应操作必须通过 ctx 方法完成,否则会静默失败或 panic。
- 设 Content-Type:
ctx.SetContentType("application/json"),不是w.Header().Set() - 写 JSON:
json.NewEncoder(ctx).Encode(data)或ctx.JSON(statusCode, data)(需自行封装) - 写原始字节:
ctx.Write(body),不是w.Write();注意它不自动加Content-Length,大响应建议手动设置ctx.Response.Header.SetContentLength(len(body)) - 错误响应:
ctx.Error("msg", fasthttp.StatusBadRequest),不是http.Error()
客户端连接池要显式配置,否则默认行为很保守
服务端性能再高,如果下游调用仍用默认 fasthttp.Client,连接复用率低、超时粗放,整体链路 QPS 依然上不去。
- 默认
Client的MaxConnsPerHost是 512,MaxIdleConnDuration是 10 秒——对高并发短连接场景不够用 - 推荐配置示例:
client := &fasthttp.Client{ MaxConnsPerHost: 1000, MaxIdleConnDuration: 30 * time.Second, ReadTimeout: 3 * time.Second, WriteTimeout: 3 * time.Second, DialDualStack: true, } - 避免全局单例
Client被多个 host 共享导致连接争抢;高频调用不同域名时,按 host 分配独立HostClient - 务必用
client.Do(req, resp),而不是client.Get()等快捷方法——后者每次都会新建fasthttp.Response,破坏零分配优势
热路径必须规避堆分配,否则 GC 会吃掉性能增益
官方 benchmark 中 “0 allocs/op” 仅在严格控制变量生命周期、避免逃逸的前提下成立。实际代码中一个 fmt.Sprintf 或未声明大小的 make([]byte, 0) 就会让分配数飙升。
- 日志打点别用
fmt.Printf,改用fasthttp.StringBuilder或预分配[]byte - 解析 query/form 时,优先用
ctx.QueryArgs().Peek("key"),而非转成string再比较(Peek返回[]byte,避免分配) - 字符串拼接用
fasthttp.AppendString或bytes.Buffer(配合sync.Pool复用) - 用
go tool trace抓取运行时分配热点,重点优化 handler 内部循环和高频路径
真正卡住 QPS 上限的,往往不是框架选型,而是 ctx 生命周期管理是否严谨、连接池是否贴合业务流量特征、以及热路径有没有意外触发堆分配——这三个地方没理清,换再快的引擎也白搭。


















