突发流量导致go服务内存崩掉,根本原因是无节制创建且未回收goroutine,使堆内存被快速耗尽;每秒1000请求若每个启1个goroutine且任务阻塞,几万goroutine堆积会令runtime.readmemstats中alloc和heapobjects指数上涨,触发oom killer杀进程。

为什么突发流量会让Go服务内存崩掉
不是goroutine本身重,而是无节制创建+未回收会快速吃光堆内存。比如每秒1000个请求,每个开1个goroutine处理,若任务执行慢或阻塞,goroutine堆积到几万,runtime.ReadMemStats里Alloc和HeapObjects会指数级上涨,触发OOM Killer直接杀进程。
ants.Pool必须配非阻塞+panic捕获
默认ants.NewPool(100)是阻塞模式:任务超容量就卡住,上游超时、重试、连接堆积,反而加剧内存压力。必须显式开启非阻塞,并确保panic不逃逸:
-
ants.NewPool(100, ants.WithNonblocking(true))—— 超限时直接返回ErrPoolExhausted,由业务决定丢弃或降级 - 所有提交的任务函数必须包裹recover:
func() { defer func() { recover() }(); /* 你的逻辑 */ } - 避免在任务里启动子goroutine却不控制生命周期,否则协程池只管worker,不管里面再起的goroutine
Pool大小不能拍脑袋定,得看实际RT和QPS
公式很简单:pool size ≈ QPS × 平均任务耗时(秒)。例如接口P99 RT是200ms,峰值QPS是500,理论需要100个worker。但得留余量:
- RT波动大(比如依赖下游gRPC偶尔卡顿),建议×1.5~2倍
- 任务含I/O等待(如HTTP调用、DB查询),实际并发度远低于CPU密集型,可适当放大
- 别设成10000这种整数——容易掩盖真实瓶颈;先从50起步压测,观察
pool.Running()和pool.Free()水位变化
释放Pool前必须等任务自然结束
pool.Release()不是立即杀光所有goroutine,而是发信号让worker做完手头任务后退出。如果在HTTP handler里每次请求都NewPool再Release,反而造成高频GC和调度抖动:
- Pool必须全局复用,通常在
init()或main()里初始化一次 - 服务退出时调用
Release(),但要配合sync.WaitGroup或context.WithTimeout等机制等待正在运行的任务完成 - 别在HTTP handler里调
pool.Submit后立刻pool.Release()——这是典型误用,会导致submit失败或panic
真正容易被忽略的是:协程池只解决goroutine数量问题,不解决任务内部的资源泄漏。比如一个任务里http.Get没关body、缓存没设TTL、日志打太猛,Pool再稳也救不了内存。











