您的位置 首页 > 德语词汇

roundtrip是什么意思,roundtrip的意思翻译、用法、同义(走进Golang之Context的使用)

大家好,今天给各位分享roundtrip是什么意思,roundtrip的意思翻译、用法、同义的一些知识,其中也会对走进Golang之Context的使用进行解释,文章篇幅可能偏长,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在就马上开始吧!

以下文章来源于大愚Talk,作者大愚Talk

我们为什么需要Context的呢?我们来看看看一个HTTP请求的处理:

roundtrip是什么意思,roundtrip的意思翻译、用法、同义(走进Golang之Context的使用)

例子大概意思是说,有一个获取订单详情的请求,会单独起一个goroutine去处理该请求。在该请求内部又有三个分支goroutine分别处理订单详情、推荐商品、物流信息;每个分支可能又需要单独调用DB、Redis等存储组件。那么面对这个场景我们需要哪些额外的事情呢?

简单归纳就是传值、同步信号(取消、超时)。

看到这里可能有人要叫了,完全可以用channel来搞啊!那么我们看看channel是否可以满足。想一个问题,如果是微服务架构,channel怎么实现跨进程的边界呢?另外一个问题,就算不跨进程,如果嵌套很多个分支,想一想这个消息传递的复杂度。

如果是你,要实现上面的这个需求,你会怎么做?

幸好,我们不用自己每次写代码都要去实现这个很基础的能力。Golang为我们准备好了一切,就是context.Context这个包,这个包的源代码非常简单,源码部分本文会略过,下期单独一篇文章来讲,本篇我们重点谈正确的使用。

Context的结构非常简单,它是一个接口。

//Context提供跨越API的截止时间获取,取消信号,以及请求范围值的功能。\n//它的这些方案在多个goroutine中使用是安全的\ntypeContextinterface{\n//如果设置了截止时间,这个方法ok会是true,并返回设置的截止时间\nDeadline()(deadlinetime.Time,okbool)\n\n//如果Context超时或者主动取消返回一个关闭的channel,如果返回的是nil,表示这个\n//context永远不会关闭,比如:Background()\nDone()<-chanstruct{}\n\n//返回发生的错误\nErr()error\n\n//它的作用就是传值\nValue(keyinterface{})interface{}\n}

写到这里,我们打住想一想,如果你来实现这样一个能力的package,你抽象的接口是否也是具备这样四个能力?

在我们开始自己鼓捣前,我们先看看net/http这个包是怎么使用的。

funcmain(){\nreq,_:=http.NewRequest("GET","https://api.github.com/users/helei112g",nil)\n\n//这里设置了超时时间\nctx,cancel:=context.WithTimeout(context.Background(),time.Millisecond*1)\ndefercancel()\nreq=req.WithContext(ctx)\n\nresp,err:=http.DefaultClient.Do(req)\niferr!=nil{\nlog.Fatalln("requestErr",err.Error())\n}\ndeferresp.Body.Close()\n\nbody,_:=ioutil.ReadAll(resp.Body)\nfmt.Println(string(body))\n}

上面这段程序就是请求github获取用户信息的接口,通过context包设置了请求超时时间是1ms(肯定无法访问到)。执行时我们看到控制台做如下输出:

2020/xx/xxxx:xx:xxrequestErrGethttps://api.github.com/users/helei112g:contextdeadlineexceeded\nexitstatus1

我们继续做实验,将上面的代码稍作修改。

funcmain(){\nreq,_:=http.NewRequest("GET","https://api.github.com/users/helei112g",nil)\n\n//这里超时改成了10s,怎么都够了吧\nctx,cancel:=context.WithTimeout(context.Background(),time.Second*10)\n//但是这里移出了defer关键字\ncancel()\nreq=req.WithContext(ctx)\n\n//没有改动的部分,省略\n......\n}

大家猜猜看能否获取到请求结果?肯定是不能的,因为context取消的信号,在net/http包内部通过ctx.Done()是能够拿到的,一旦获取到就会进行取消。上面的代码,控制台会输出:

2020/xx/xxxx:xx:xxrequestErrGethttps://api.github.com/users/helei112g:contextcanceled\nexitstatus1

注意两次控制台输出的错误信息是不一样的。

接下来,我们去看看net/http包内部是怎么捕捉信号的,我们只关注context的部分,其它的直接忽略,源码路径如下;

net/http/transport.go(go1.13.7)

//req就是我们上面传进来的req,它有个context字段\nfunc(t*Transport)roundTrip(req*Request)(*Response,error){\nt.nextProtoOnce.Do(t.onceSetNextProtoDefaults)\nctx:=req.Context()//获取了context\ntrace:=httptrace.ContextClientTrace(ctx)//这里内部实际用到了context.Value()方法\n\n//各种处理,无关代码删除了\n\n//处理请求\nfor{\n//检查是否关闭了,如果关闭了就直接返回\nselect{\ncase<-ctx.Done():\nreq.closeBody()\nreturnnil,ctx.Err()\ndefault:\n}\n\n//发送请求出去\n}\n}

来总结下上面这段代码,实际上关于context的精髓就在for循环中的select,它通过ctx.Done()来获取信号,因为不管是自动超时,还是主动取消,ctx.Done()都会收到一个关闭的channel的信号。

这里隐藏了一个细节,那就是如果按照上面的逻辑只能处理到发起请求前的超时,但是如果请求已经被发出去了,等待这段时间的超时该如何控制呢?感兴趣的小伙伴可以去看源码的这里:

net/http/transport.go:1234(go1.13.7)

其实就是在内部等待返回的时候不断的检查ctx.Done()信号,如果发现了就立即返回。

好了,官方的技巧我们已经学完了,现在轮到我们把开头的例子写个代码来实现下。

由于服务内部不方便模拟,我们简化成函数调用,假设图中所有的逻辑都可以并发调用。现在我们的要求是:

为了清晰,我这里所有接口都返回一个字符串,实际中会根据需要返回不同的结果;请求参数也都只使用了context。代码如下:

typekeyint\n\nconst(\nuserIP=iota\nuserID\nlogID\n)\n\ntypeResultstruct{\norderstring\nlogisticsstring\nrecommendstring\n}\n\n//timeout:1s\n//入口函数\nfuncapi()(result*Result,errerror){\nctx,cancel:=context.WithTimeout(context.Background(),time.Second*1)\ndefercancel()\n\n//设置值\nctx=context.WithValue(ctx,userIP,"127.0.0.1")\nctx=context.WithValue(ctx,userID,666888)\nctx=context.WithValue(ctx,logID,"123456")\n\nresult=&Result{}\n//业务逻辑处理放到协程中\ngofunc(){\nresult.order,err=getOrderDetail(ctx)\n}()\ngofunc(){\nresult.logistics,err=getLogisticsDetail(ctx)\n}()\ngofunc(){\nresult.recommend,err=getRecommend(ctx)\n}()\n\nfor{\nselect{\ncase<-ctx.Done():\nreturnresult,ctx.Err()//取消或者超时,把现有已经拿到的结果返回\ndefault:\n\n}\n\n//有错误直接返回\niferr!=nil{\nreturnresult,err\n}\n\n//全部处理完成,直接返回\nifresult.order!=""&&result.logistics!=""&&result.recommend!=""{\nreturnresult,nil\n}\n}\n}\n\n//timeout:500ms\nfuncgetOrderDetail(ctxcontext.Context)(string,error){\nctx,cancel:=context.WithTimeout(ctx,time.Millisecond*500)\ndefercancel()\n\n//模拟超时\ntime.Sleep(time.Millisecond*700)\n\n//获取userid\nuip:=ctx.Value(userIP).(string)\nfmt.Println("userIP",uip)\n\nreturnhandleTimeout(ctx,func()string{\nreturn"order"\n})\n}\n\n//timeout:700ms\nfuncgetLogisticsDetail(ctxcontext.Context)(string,error){\nctx,cancel:=context.WithTimeout(ctx,time.Millisecond*700)\ndefercancel()\n\n//获取userid\nuid:=ctx.Value(userID).(int)\nfmt.Println("userID",uid)\n\nreturnhandleTimeout(ctx,func()string{\nreturn"logistics"\n})\n}\n\n//timeout:400ms\nfuncgetRecommend(ctxcontext.Context)(string,error){\nctx,cancel:=context.WithTimeout(ctx,time.Millisecond*400)\ndefercancel()\n\n//获取logid\nlid:=ctx.Value(logID).(string)\nfmt.Println("logID",lid)\n\nreturnhandleTimeout(ctx,func()string{\nreturn"recommend"\n})\n}\n\n//超时的统一处理代码\nfunchandleTimeout(ctxcontext.Context,ffunc()string)(string,error){\n//请求之前先去检查下是否超时\nselect{\ncase<-ctx.Done():\nreturn"",ctx.Err()\ndefault:\n}\n\nstr:=make(chanstring)\ngofunc(){\n//业务逻辑\nstr<-f()\n}()\n\nselect{\ncase<-ctx.Done():\nreturn"",ctx.Err()\ncaseret:=<-str:\nreturnret,nil\n}\n}

不知道你是否看明白了整个使用,我们这个例子看起来很复杂,实际上与我给你介绍的net/http包控制超时是一样的,只不过net/http的控制超时代码不需要我们写,而且我们这里一次性把三个调用的整合到了一起。

还有一点说明一下,对于select,如果没有写defalut分支,是不需要放在for循环中的,因为它本身就会阻塞(网络上有很多例子放在for循环中)。

roundtrip是什么意思,roundtrip的意思翻译、用法、同义和走进Golang之Context的使用的问题分享结束啦,以上的文章解决了您的问题吗?欢迎您下次再来哦!

本站涵盖的内容、图片、视频等数据,部分未能与原作者取得联系。若涉及版权问题,请及时通知我们并提供相关证明材料,我们将及时予以删除!谢谢大家的理解与支持!

Copyright © 2023