所有启动goroutine的函数若涉及网络、数据库或RPC调用,必须接收context.Context参数;否则无法被外部取消,导致资源泄漏。错误写法:go fetchUser(id);正确写法:go fetchUser(ctx, id),且内部需监听ctx.Done()。

并发任务必须带 context.Context 参数
所有启动 goroutine 的函数,只要可能涉及网络、数据库或 RPC 调用,就必须接收 context.Context 参数。这不是风格问题,而是资源安全底线——没有 context 的 goroutine 无法被外部取消,一旦父请求超时或中断,它会继续运行并持有连接、内存和 goroutine 栈。
- 错误写法:
go fetchUser(id)(fetchUser不接受 context) - 正确写法:
go fetchUser(ctx, id),且fetchUser内部需监听 - 常见漏点:调用第三方 SDK 时,忽略其 context 参数;或在
select中漏掉case - 注意:不能用
context.Background()或context.TODO()替代传入的请求 context,否则取消信号无法向下传递
errgroup.Group 是微服务并发最实用的封装
单靠 sync.WaitGroup 无法处理失败传播,而裸用 context.WithCancel 又要手动管理 cancel 函数。微服务中推荐直接用 errgroup.Group,它把 WaitGroup + Context + 错误聚合三者打包好了。
- 统一取消:任一子任务返回非 nil error,整个
errgroup会自动 cancel 其余正在运行的任务 - 错误聚合:
g.Wait()返回第一个发生的 error,适合快速失败场景 - 控制并发数:用
g.SetLimit(n)限制同时最多 n 个 goroutine 运行(内部基于 channel 实现) - 注意:
g.Go(func(ctx context.Context) error { ... })中的ctx已自动继承自父 context,无需再调context.WithXXX
WithTimeout 和 WithDeadline 的选择取决于业务语义
超时不是越短越好,也不是写死一个值就完事。微服务链路中,超时必须逐层收敛,且类型要匹配实际约束条件。
Colly 是一个用于 Go 语言的快速开源爬取和爬虫框架。它适用于从简单的页面提取到异步爬虫处理大量页面集合,支持请求回调和结构化解析。
- 用
context.WithTimeout(ctx, 2*time.Second)表示“从现在起最多等 2 秒”——适用于下游响应时间不可控、但可容忍固定延迟的场景(如缓存穿透兜底) - 用
context.WithDeadline(ctx, deadline)表示“必须在某个绝对时间点前完成”——适用于与外部系统约定 SLA、或配合上游截止时间(如 gRPC gateway 传入的grpc-timeoutheader) - 错误实践:在 HTTP handler 中对所有下游调用都设相同 timeout,忽略各依赖的真实 P99 延迟差异,导致级联雪崩
- 关键点:deadline 应由入口处统一计算,子服务不应自行重置或延长
Value 传递只用于请求范围元数据,禁止传业务参数
context.WithValue 是唯一允许往 context 塞键值对的方法,但它极易滥用。它的存在意义仅限于透传请求生命周期内只读的元数据,比如 traceID、userID、tenantID。
立即学习“go语言免费学习笔记(深入)”;
- 允许:
ctx = context.WithValue(ctx, keyTraceID, "abc123"),后续中间件和日志可提取 - 禁止:
ctx = context.WithValue(ctx, keyUserID, userID)然后在 service 层用ctx.Value(keyUserID)取值——这破坏了函数签名清晰性,也绕过了类型检查 - 更糟的是:用
context.WithValue传 struct 指针或 map,造成竞态或内存泄漏 - 替代方案:业务参数应显式作为函数参数传入;若大量函数都需要同一组值,考虑封装为 struct 并作为 receiver

















