直接用 go handler() 会崩,是因为调度器无法承受无序堆积的 goroutine;每个 goroutine 需排队进全局队列、被 p 抢占、触发栈扩容和 gc 扫描,qps 上千且含 i/o 时 goroutine 数迅速破万,超 5000 即属失控。

为什么直接用 go handler() 会崩
不是协程不够轻,是调度器扛不住无序堆积的 goroutine。每个 go handler() 启动的 goroutine 都要排队进全局队列、被 P 抢占、触发栈扩容和 GC 扫描。QPS 上千、带 DB 或 HTTP 调用时,runtime.NumGoroutine() 很快破万,持续 >5000 就该警觉——这不是高并发,是失控。
- 别在 handler 里无条件
go,尤其当逻辑含 I/O 或网络调用 - 阻塞式写入任务 channel(如
ch )等于放弃限流能力,必须用 <code>select { case ch 或带超时的 <code>select - worker 数量不硬绑 CPU 核数:IO 密集型可设为
2 * runtime.NumCPU()起步;纯计算型必须 ≤runtime.NumCPU() - 每个 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 悬挂
如何防止 http.Server 被慢连接拖垮
默认配置没设超时,一个慢请求或恶意大头就能让连接堆积、文件描述符耗尽、后续请求全部 hang 住。
-
ReadTimeout控制读请求头和 body 的总时间,建议 5–10s -
WriteTimeout控制响应写出时间,建议 ≥ReadTimeout,留出处理余量 -
IdleTimeout控制连接空闲存活时间,设为 60–90s,比客户端预期更短一点 -
MaxHeaderBytes必须显式设(如 8192),防大头攻击,否则默认 1MB 可能被滥用 - 不要用
http.ListenAndServe(),改用自定义http.Server实例并显式配置
goroutine 泄漏和内存压力怎么快速定位
线上服务跑几天后 NumGoroutine 持续上涨、GC 频次变高、P99 延迟毛刺增多,大概率是泄漏或高频分配没控住。
- 用
pprof抓/debug/pprof/goroutine?debug=2查阻塞点,重点关注没defer resp.Body.Close()或没退出的 channel receive - 用
pprof抓/debug/pprof/heap看 top allocs,高频 new 的结构体(如bytes.Buffer、json.RawMessage)优先丢进sync.Pool - 批量 JSON 解析别用
json.Unmarshal([]byte),改用json.NewDecoder(r.Body)流式解,减少中间 byte slice 分配 - 别在 handler 里
recover()后吞掉 panic —— 该日志打日志,该告警告警,否则错误静默积累
真正难的不是堆参数,是把“启动权”收回来:谁创建 goroutine、谁回收连接、谁释放内存、谁决定超时,都得有明确归属。线上跑着的服务,runtime.NumGoroutine() 和 runtime.ReadMemStats() 应该是每小时自动采样的基础指标,而不是出事了才去查。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











