并发请求的错误怎么处理?
假设一个业务场景:
需求做一个 GetStudent 接口,这个接口需要请求 GetName, GetAge,GetHobby 三个 rpc 接口,为了性能开 3 个 goroutine
这三个接口都会返回 response,error,且耗时不一样
任何一个 rpc 返回了 error, GetStudent 就直接返回 nil, error,不需要等待另外 2 个rpc 返回了
三个 rpc 都没有 error, GetStudent 就把结果拼装返回
请问这种业务模型,应该怎么做呢?
正在回答
有一个golang.org/x/sync/errgroup 包帮助做这件事。不过也没有想象的那么容易。其实提前出错提前返回结果很简单,但是我们要小心goroutine leak,即使提前返回了,剩下来的任务也必须实时cancel掉。
https://play.studygolang.com/p/VJ9sOt67oJJ
package main
import (
"context"
"fmt"
"os"
"runtime"
"time"
"golang.org/x/sync/errgroup"
)
var (
Web = fakeSearch("web")
Image = fakeSearch("image")
Video = fakeSearch("video")
)
type Result string
type Search func(ctx context.Context, query string, wait time.Duration) (Result, error)
type ResultWithErr struct {
Result Result
Err error
}
func fakeSearch(kind string) Search {
return func(ctx context.Context, query string, wait time.Duration) (Result, error) {
r := <-queryRPC(ctx, kind, query, wait) // 访问RPC的结果通过一个channel返回
return r.Result, r.Err
}
}
func queryRPC(ctx context.Context, kind, query string, wait time.Duration) chan ResultWithErr {
out := make(chan ResultWithErr)
go func() {
defer close(out) // 不管是被cancel还是执行完毕,都要close,不然外面会一直等待
select {
case <-time.After(wait): // 假装执行RPC
if kind == "image" {
out <- ResultWithErr{
Err: fmt.Errorf("cannot search %s", kind),
}
} else {
out <- ResultWithErr{
Result: Result(fmt.Sprintf("%s result for %q", kind, query)),
}
}
case <-ctx.Done(): // 如果context被cancel了,我们就不等下去了
}
}()
return out
}
func main() {
fmt.Println("start")
Google := func(ctx context.Context, query string) ([]Result, error) {
g, ctx := errgroup.WithContext(ctx)
searches := []Search{Web, Image, Video}
results := make([]Result, len(searches))
for i, search := range searches {
i, search := i, search // https://golang.org/doc/faq#closures_and_goroutines
g.Go(func() error {
result, err := search(ctx, query, time.Duration(i+1)*time.Second)
if err == nil {
results[i] = result
}
fmt.Printf("search %d completed or cancelled\n", i)
return err
})
}
if err := g.Wait(); err != nil {
return nil, err
}
return results, nil
}
results, err := Google(context.Background(), "golang")
if err != nil {
fmt.Fprintln(os.Stderr, err)
} else {
for _, result := range results {
fmt.Println(result)
}
}
// 检查一下我们的goroutine数量,运行到这里必须为1,也就是只有main自己还在,不然会有goroutine leak。
fmt.Printf("goroutine count: %d\n", runtime.NumGoroutine())
}
恭喜解决一个难题,获得1积分~
来为老师/同学的回答评分吧
0 星