并发请求的错误怎么处理?

并发请求的错误怎么处理?

假设一个业务场景:

  1. 需求做一个 GetStudent 接口,这个接口需要请求 GetName, GetAge,GetHobby 三个 rpc 接口,为了性能开 3 个 goroutine

  2. 这三个接口都会返回 response,error,且耗时不一样

  3. 任何一个 rpc 返回了 error, GetStudent 就直接返回 nil, error,不需要等待另外 2 个rpc 返回了

  4. 三个 rpc 都没有 error, GetStudent 就把结果拼装返回

请问这种业务模型,应该怎么做呢?


正在回答

登陆购买课程后可参与讨论,去登陆

1回答

有一个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())

}



  • 人生几何cg 提问者 #1

    这个 范式 还是很复杂的,感觉非常容易出 运行时错误,

    这种业务场景应该挺常见的,要是能有 类似 sync,WaitGroup 这样友好的 api 就好了

    2021-12-13 21:45:44
问题已解决,确定采纳
还有疑问,暂不采纳

恭喜解决一个难题,获得1积分~

来为老师/同学的回答评分吧

0 星
请稍等 ...
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号

在线咨询

领取优惠

免费试听

领取大纲

扫描二维码,添加
你的专属老师