Go标准net/http服务在8核机器上压测得9–12万QPS属正常上限,非代码错误,而是默认设计取舍;需用wrk(如wrk -t8 -c1000 -d30s -H "Connection: close" http://localhost:8080/ping)排除连接复用干扰,确保handler无外部依赖,并配置Read/WriteTimeout防goroutine阻塞。

怎么用 wrk 压测 Go HTTP 服务的真实吞吐
Go 标准 net/http 服务在 8 核机器上压出 9–12 万 QPS 是正常现象,不是代码写错了,而是默认实现的合理上限。要拿到可信数据,必须用 wrk 这类低开销工具,而非 ab 或自写 goroutine 循环——前者会因自身连接管理拖慢结果,后者容易触发 GC 干扰测量。
实操建议:
- 固定并发连接数与线程数匹配 CPU 核心数,例如
wrk -t8 -c1000 -d30s http://localhost:8080/ping - 禁用 HTTP/1.1 的 Keep-Alive(加
-H "Connection: close")可排除连接复用干扰,专注单请求处理能力 - 确保被测 handler 不含任何外部依赖(如 DB、Redis),否则瓶颈不在 HTTP 层而在 I/O
- 压测前先跑一次
go tool pprof http://localhost:8080/debug/pprof/profile?seconds=10,确认无意外阻塞点
为什么 http.Server 的超时配置直接影响 QPS
没设 ReadTimeout 和 WriteTimeout 的服务,一个慢客户端就能卡住整个 goroutine,导致连接堆积、文件描述符耗尽、QPS 断崖下跌。这不是并发模型的问题,是资源没及时释放。
实操建议:
-
ReadTimeout控制从 TCP 建连到读完 header 的时间,建议设为5 * time.Second;若需上传大文件,单独用ReadHeaderTimeout -
WriteTimeout必须覆盖 handler 全流程(含日志、DB 写入),建议10 * time.Second,避免响应卡在中间环节 - 不要只靠
IdleTimeout——它对正在处理的慢请求完全无效 - 用
ConnState回调手动统计活跃连接数,比依赖MaxConns(Go 1.19+)更可控
怎么判断瓶颈在 CPU、内存还是 Goroutine 调度
吞吐上不去时,不能凭感觉猜。直接访问 /debug/pprof/ 下的端点,每个都对应一类瓶颈信号:
实操建议:
-
/debug/pprof/profile?seconds=30:CPU 热点集中于某个 handler 函数?说明逻辑太重,该拆或缓存 -
/debug/pprof/heap:看inuse_space是否随请求线性增长?若持续上涨,大概率是对象没复用(比如反复make([]byte, ...)) -
/debug/pprof/goroutine?debug=1:如果数量稳定在几百但 QPS 卡住,说明有 goroutine 阻塞在 channel 或锁上;若每秒新增上千个且不退出,就是泄漏 -
/debug/pprof/allocs:分配速率远高于heap中存活对象数?说明 GC 压力大,该上sync.Pool
哪些优化能真正提升吞吐,哪些只是幻觉
很多“优化”在真实压测中毫无作用,甚至有害。关键看是否减少系统调用、内存分配或上下文切换。
实操建议:
- 启用 HTTP/2 只有在 TLS + 客户端支持的前提下才有效;纯 HTTP/1.1 下强行切协议层无收益
-
sync.Pool对高频小对象(如 buffer、JSON 解析器)有效,但对大结构体或生命周期长的对象反而增加逃逸风险 - 调大
MaxHeaderBytes到1 (1MB)可能让恶意请求轻易耗尽内存,应按业务实际设为8 (8KB) - 用
jsoniter替换encoding/json在解析深度嵌套结构时明显更快,但对扁平 JSON 提升微乎其微
真实瓶颈往往藏在 handler 内部的同步阻塞调用里,比如没带 context 超时的数据库查询、未设 deadline 的 HTTP client 请求,或者一个没加锁保护的全局 map 写操作——这些不会出现在 pprof 的 top 函数里,但会让 QPS 归零。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











