Go Web服务器QPS上不去主因是请求排队,非语言问题;真实瓶颈常在数据库连接池耗尽、Redis阻塞、HTTP超时过宽或goroutine泄漏,需通过MySQL线程数、Redis延迟、pprof协程分析及容器CPU限制对齐来精准定位。

Go Web 服务器 QPS 上不去,90% 不是语言问题,而是请求在某个环节排队——数据库连接池耗尽、Redis 阻塞、HTTP 超时设置过宽、或 goroutine 没被正确回收。直接调高 GOMAXPROCS 或加机器只会掩盖真实瓶颈。
查清真实瓶颈:别信 ab -c 100 的结果
用 ab -n 100 -c 100 测出延迟飙升,不代表 Go 处理慢,大概率是后端依赖(如 MySQL、Redis)已满负荷。真实瓶颈往往藏在:
-
SHOW STATUS LIKE 'Threads_running'—— MySQL 正在执行的线程数超 5–10 就要警惕 -
Innodb_row_lock_waits持续增长,说明写锁争用严重 -
redis-cli --latency查 Redis 实际响应毛刺 - Go 服务自身
net/http/pprof/goroutine?debug=2看是否堆积大量阻塞在redisClient.Get或db.Query
HTTP 层必须收紧的三个超时参数
默认的 http.Server 没设超时,一个慢客户端或异常网络就能拖垮整台机器。必须显式配置:
-
ReadTimeout: 5 * time.Second—— 防止慢请求占用连接太久 -
WriteTimeout: 10 * time.Second—— 避免大响应体或下游卡顿导致协程 hang 住 -
IdleTimeout: 60 * time.Second—— 主动回收空闲长连接,避免连接池被占满
注意:这些值不是越小越好,需结合业务容忍度。比如文件下载接口可放宽 WriteTimeout,但推荐 API 必须严格控制在 10 秒内。
缓存层不加锁,但得防穿透和雪崩
用 Redis 缓存时,cache miss 后直接查 DB 再回填,高并发下会瞬间打穿 DB。必须做两件事:
- 加
sync.Once或分布式锁(如 Redis SETNX + 过期时间)保证同一 key 只有一个请求回源 - 对热点 key 设置随机过期时间(如
10*time.Minute + time.Duration(rand.Int63n(60))*time.Second),避免集体失效 - 兜底用
sync.Map做本地缓存(如用户权限、配置项),减少 Redis 往返
goroutine 泄漏比性能差更危险
QPS 稳定但内存持续上涨?十有八九是 goroutine 没退出。常见陷阱:
- 忘记给
context.WithTimeout的子 context 调用cancel() - channel 发送未关闭,接收方一直
range等着 - HTTP handler 里启了 goroutine 但没处理 panic,导致协程静默卡死
上线前必跑:go tool pprof http://localhost:6060/debug/pprof/goroutine?debug=2,看是否有成百上千个状态为 select 或 chan receive 的 goroutine。
最易被忽略的是容器环境下的 CPU 绑定——宿主机 8 核、容器只分到 2 核,但 Go 默认启动 8 个 P,结果 6 个 P 在抢 2 个 CPU 时间片。务必通过 GOMAXPROCS=2 或代码中 runtime.GOMAXPROCS(2) 对齐容器限制。


















