直接用 go handler() 会崩是因为调度器扛不住无序堆积的 goroutine,而非协程不够轻;每个 goroutine 都需排队、抢占、栈扩容和 gc 扫描,qps 上千时数量易破万,numgoroutine >5000 即属失控。

为什么直接用 go handler() 会崩
不是协程不够轻,是调度器扛不住无序堆积。每个 go 启动的 goroutine 都要进全局队列排队、被 P 抢占、触发栈扩容和 GC 扫描。当 QPS 上千、任务又含 DB 查询或 HTTP 调用时,goroutine 数可能飙到上万,runtime.NumGoroutine() 持续 >5000 就该警觉了——这不是并发高,是失控。
真正该做的,是把“启动权”收回来:
- 用带缓冲的
chan做任务队列,容量设为 100–1000(别盲目堆大,内存占用和延迟成正比) - worker 数量不硬绑 CPU 核数:IO 密集型服务可设为
2 * runtime.NumCPU()起步;纯计算型必须 ≤runtime.NumCPU() - 提交任务必须用
select { case ch ,阻塞式写入等于放弃限流能力 - 每个 worker 内部必须带
context.WithTimeout(),防止单个慢任务拖垮整个池
http.Transport 连接池参数怎么不踩坑
默认的 http.DefaultClient 在高并发下几乎必出问题:空闲连接太少、复用超时太短、每 host 限制太死。典型现象是大量 dial tcp: lookup xxx: no such host 或 http: server closed idle connection 错误,本质是连接反复建/毁。
关键三项必须显式配置:
-
MaxIdleConns:建议设为 100,全局最大空闲连接数,低于此值才新建连接 -
MaxIdleConnsPerHost:必须 ≥ 20(HTTP/1.1 默认仅 2),否则同一域名请求全卡在连接等待 -
IdleConnTimeout:设为 90s,比服务端IdleTimeout略短,避免被对方主动断连后还傻等
别漏掉 KeepAlive 和 TLSHandshakeTimeout:前者保持长连接活跃,后者防 TLS 握手卡死导致 goroutine 悬挂。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
数据库连接池和 HTTP 服务层怎么协同不打架
DB 连接池(sql.DB)和 HTTP 服务层的协程池是两套独立资源,但共用同一组系统资源(文件描述符、内存、CPU)。常见错误是:HTTP 层开了 50 个 worker,每个 worker 查一次 DB,而 db.SetMaxOpenConns(20) ——结果 30 个请求在等 DB 连接,排队超时,但 HTTP worker 还在傻等。
协同要点就一条:让 DB 池子的吞吐 ≥ HTTP 池子的并发上限。
- 查
db.Stats()中的WaitCount:持续上涨说明 DB 连接不够,优先调高SetMaxOpenConns -
SetMaxIdleConns建议设为和SetMaxOpenConns相同,避免空闲连接过早回收 - HTTP Server 的
ReadTimeout/WriteTimeout必须 context.Timeout(DB 查询)db.ConnMaxLifetime,形成超时嵌套链,不漏死角 - OS 层也要跟上:
net.core.somaxconn至少 65535,ulimit -n≥ 100000
监控指标里哪些值一涨就得立刻干预
调参不是调完就完事。真正决定是否有效的,是这几个运行时数字:
-
runtime.NumGoroutine():稳定在2 * worker 数 + 100附近才算健康;超过 3 倍说明有 goroutine 泄漏(比如忘了close(ch)或没读响应体) -
db.Stats().WaitCount:每秒增长 > 0.5 就得加连接,> 5 就算严重排队 -
http.Server的IdleTimeout触发频次:用expvar暴露,突增说明客户端异常或网络不稳定 - 自定义协程池里的任务排队长度:如果常驻 > 缓冲区 80%,要么压测流量真来了,要么 worker 数配少了
这些值没法靠经验预估,必须接入 Prometheus + Grafana 实时看,尤其在发布前后盯住 15 分钟。一个没暴露 WaitCount 的 DB 池,和没装仪表盘的跑车没区别。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










