一个抓取服务要并发请求 N 个上游,任何一个关键上游失败就应该整体取消、返回错误。裸用 sync.WaitGroup + 手动 context 取消很啰嗦,errgroup 正好解决这个。
g, ctx := errgroup.WithContext(context.Background())
for _, u := range urls {
u := u
g.Go(func() error {
req, _ := http.NewRequestWithContext(ctx, "GET", u, nil)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err // 任一返回 error,ctx 会被取消
}
defer resp.Body.Close()
return process(resp)
})
}
if err := g.Wait(); err != nil {
log.Fatal(err)
}
errgroup.WithContext 返回的 ctx,会在第一个 goroutine 返回非 nil error(或 Wait 返回)时被取消。ctx 发请求/派生,取消信号才能传播下去,否则别人取消了它还在跑。g.SetLimit(n)(新版 golang.org/x/sync)或带缓冲信号量,避免一次打爆下游。goroutine 里往无缓冲 channel 发送却没人接收、或者没监听 ctx.Done(),会导致任务结束后 goroutine 挂住。用 go test -race 和 pprof 的 goroutine profile 能很快发现这类泄漏。
— 本文为个人运维笔记,如有疏漏欢迎指正。