不能在 http handler 中裸写 go sendemail(),因存在五类风险:panic 不捕获致协程静默退出、无 context.withtimeout 导致任务卡死、无并发限制引发 oom、无 channel 缓冲易造成调度雪崩。

微服务里直接 go func() {}() 处理请求,90% 的线上故障都从这儿开始。
为什么不能在 HTTP handler 里裸写 go sendEmail()
看似省事,实际埋了五个雷:
- panic 不被捕获,整个 goroutine 消失,日志都没留,任务静默失败
- 没设
context.WithTimeout,邮件发到一半卡住,协程永远挂起 - 没限并发,突发流量打进来,瞬间 spawn 几千 goroutine,OOM 或调度器雪崩
- 没 channel 缓冲,
taskCh 阻塞在 handler 里,HTTP 连接卡死、超时、客户端重试放大压力 - 服务关闭时,这些 goroutine 还在跑,既不响应
ctx.Done(),也不释放连接或文件句柄
make(chan Task, N) 的缓冲大小怎么定
不是拍脑袋写 1000,而是按真实负载估算:
- 取最近 7 天峰值 QPS(比如 2400 req/s)
- 乘以该任务平均耗时(比如发短信平均 200ms → 0.2s)
- 结果是
2400 × 0.2 = 480,向上取整 →make(chan Task, 512) - 若任务类型混杂(支付校验 vs 日志上报),必须拆成多个独立 channel,避免慢任务堵死快通道
缓冲太小:生产者频繁阻塞,API 响应抖动;太大:内存浪费,且掩盖了消费能力不足的真实问题。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
worker goroutine 必须带 recover 和显式退出控制
一个健壮的 worker 长这样:
func worker(ctx context.Context, ch
- 不依赖
for range ch—— 它无法响应上下文取消 - 不把
task.Run()包在 defer 里 —— 错误无法透出,也无法做重试判断 - 不共用全局 logger 实例 —— 每个 task 应带独立 traceID,否则日志串扰查不到根因
需要结果返回时,别用全局 map 存 taskID → result
高频场景下 map 竞态 + GC 压力大,更稳妥的做法是:
- 每个 Task 带一个
resultCh chan 字段 - worker 执行完直接
task.resultCh ,无需锁 - 调用方用
select { case res := 控制等待 - channel 容量设为 1,避免结果堆积;发送后立即 close(resultCh),防止接收方永久阻塞
真正难的不是“怎么让任务异步”,而是“怎么让异步变得可观察、可中断、可回溯”。channel 和 goroutine 是工具,不是答案——它们只放大你设计里的漏洞,从不掩盖。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










