net/http默认配置因保守的连接池参数易成吞吐瓶颈,需调优MaxIdleConns等参数;fasthttp虽吞吐高但不兼容HTTP/2和中间件生态,适用场景有限;bufio缓冲可降系统调用频次,但需匹配协议特性。

为什么 net/http 默认配置会成为吞吐瓶颈
Go 的 http.Server 默认启用了连接复用(keep-alive)和长连接,但默认的 MaxIdleConns、MaxIdleConnsPerHost 和 IdleConnTimeout 都偏保守。在高并发短连接或大量后端调用场景下,你可能看到大量 http: TLS handshake timeout 或 http: server gave HTTP response to HTTPS client 类似错误——其实根本不是 TLS 问题,而是连接池耗尽或过早关闭导致重试堆积。
实操建议:
- 显式配置
http.Transport:把MaxIdleConns设为200或更高,MaxIdleConnsPerHost至少匹配后端实例数,IdleConnTimeout建议设为30 * time.Second(太长易占资源,太短频繁重建) - 禁用
ExpectContinueTimeout(设为 0),避免小请求卡在 100-continue 等待上 - 若用
http.Client调用外部服务,务必复用单例,不要每次 new
什么时候该换 fasthttp,又为什么不能盲目换
fasthttp 在纯吞吐指标上常比 net/http 高 2–5 倍,核心是绕过了 net/http 的标准 Request/Response 对象分配和反射解析。但它不兼容 http.Handler 接口,也不支持 HTTP/2、Streaming Response 等特性。
适用场景很明确:
立即学习“go语言免费学习笔记(深入)”;
- 内部 RPC 网关、反向代理、静态文件转发等协议简单、路径固定、无需中间件生态的场景
- 压测时发现
net/http的 GC 压力集中在http.Header和bytes.Buffer分配上(pprof 查runtime.mallocgc占比 >40%)
踩坑提醒:
-
fasthttp.RequestCtx.URI().QueryString()返回的是字节切片视图,不能直接赋值给 string 后长期持有(底层内存会被复用) - 它默认不校验 Host 头,需手动检查
ctx.Request.Header.Host()防域前置攻击 - 日志、鉴权、trace 等逻辑得重写适配,无法复用
chi/gorilla/mux生态
bufio.Reader/Writer 手动缓冲对吞吐的真实影响
Go 的 net.Conn 默认不带缓冲,每次 Read() 或 Write() 都可能触发一次系统调用。尤其在处理小包(如 MQTT 心跳、Protobuf 小消息)时,syscall.read 频次飙升,strace -e trace=read,write 可明显观察到。
关键不是“加不加”,而是“加在哪”:
- 服务端:在
conn上套一层bufio.NewReaderSize(conn, 8192),能显著降低 read 次数;但bufio.NewWriterSize要慎用——若业务逻辑有流式响应(如 SSE),延迟刷写会导致首字节延迟 - 客户端:用
bufio.ReadWriter包裹net.Conn,尤其在自定义协议(如 Redis RESP、自研二进制协议)中收益最大 - 注意:HTTP/1.1 的 chunked 编码、
Transfer-Encoding: identity等场景,缓冲区大小要与分块边界对齐,否则可能卡住
连接数打满却 CPU 很低?查查 epoll_wait 和 Goroutine 阻塞点
常见现象:QPS 上不去,top 显示 CPU 不高,ss -s 显示 ESTAB 连接数接近 ulimit,但 go tool pprof 的 goroutine profile 里大量 goroutine 停在 net.(*pollDesc).wait 或 runtime.gopark ——说明不是计算瓶颈,而是 I/O 等待或锁竞争。
排查优先级:
- 先看
lsof -p $PID | wc -l是否接近系统fs.file-max,是则调大ulimit -n并检查连接泄漏(defer 中漏掉resp.Body.Close()是高频原因) - 用
go tool trace抓 5 秒 trace,重点关注 “Network blocking profile” 和 “Synchronization blocking profile”,确认是否卡在 mutex 或 channel receive - 若用
sync.Pool复用http.Request或bytes.Buffer,注意 Pool 的 Get/Put 必须成对,且对象不能跨 goroutine 传递(会触发 panic)
真正卡住吞吐的,往往不是代码写得多快,而是连接怎么建、怎么关、怎么复用——这些细节没对齐业务模型,再好的算法也跑不满网卡。











