Golang自带的HttpClient超时机制怎么实现


本篇内容主要讲解“Golang自带的HttpClient超时机制怎么实现”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Golang自带的HttpClient超时机制怎么实现”吧!在介绍 Go 的 HttpClient 超时机制之前,我们先看看 Java 是如何实现超时的。写一个 Java 原生的 HttpClient,设置连接超时、读取超时时间分别对应到底层的方法分别是:再追溯到 JVM 源码,发现是对系统调用的封装,其实不光是 Java,大部分的编程语言都借助了操作系统提供的超时能力。然而 Go 的 HttpClient 却提供了另一种超时机制,挺有意思,我们来盘一盘。但在开始之前,我们先了解一下 Go 的 Context。Context 是什么?根据 Go 源码的注释:// A Context carries a deadline, a cancellation signal, and other values across
// API boundaries.
// Context’s methods may be called by multiple goroutines simultaneously.Context 简单来说是一个可以携带超时时间、取消信号和其他数据的接口,Context 的方法会被多个协程同时调用。Context 有点类似 Java 的ThreadLocal,可以在线程中传递数据,但又不完全相同,它是显示传递,ThreadLocal 是隐式传递,除了传递数据之外,Context 还能携带超时时间、取消信号。Context 只是定义了接口,具体的实现在 Go 中提供了几个:Background :空的实现,啥也没做TODO:还不知道用什么 Context,先用 TODO 代替,也是啥也没做的空 ContextcancelCtx:可以取消的 ContexttimerCtx:主动超时的 Context针对 Context 的三个特性,可以通过 Go 提供的 Context 实现以及源码中的例子来进一步了解下。Context 三个特性例子这部分的例子来源于 Go 的源码,位于 src/context/example_test.go使用 context.WithValue 来携带,使用 Value 来取值,源码中的例子如下:

//来自src/context/example_test.go
funcExampleWithValue(){
	typefavContextKeystring

	f:=func(ctxcontext.Context,kfavContextKey){
免费云主机域名
		ifv:=ctx.Value(k);v!=nil{
			fmt.Println("foundvalue:",v)
			return
		}
		fmt.Println("keynotfound:",k)
	}

	k:=favContextKey("language")
	ctx:=context.WithValue(context.Background(),k,"Go")

	f(ctx,k)
	f(ctx,favContextKey("color"))

	//Output:
	//foundvalue:Go
	//keynotfound:color
}

先起一个协程执行一个死循环,不停地往 channel 中写数据,同时监听 ctx.Done() 的事件

//来自src/context/example_test.go
gen:=func(ctxcontext.Context)

然后通过 context.WithCancel 生成一个可取消的 Context,传入 gen 方法,直到 gen 返回 5 时,调用 cancel 取消 gen 方法的执行。

//来自src/context/example_test.go
ctx,cancel:=context.WithCancel(context.Background())
defercancel()//cancelwhenwearefinishedconsumingintegers

forn:=rangegen(ctx){
	fmt.Println(n)
	ifn==5{
		break
	}
}
//Output:
//1
//2
//3
//4
//5

这么看起来,可以简单理解为在一个协程的循环中埋入结束标志,另一个协程去设置这个结束标志。

然后通过 context.WithCancel 生成一个可取消的 Context,传入 gen 方法,直到 gen 返回 5 时,调用 cancel 取消 gen 方法的执行。
这么看起来,可以简单理解为在一个协程的循环中埋入结束标志,另一个协程去设置这个结束标志。
有了 cancel 的铺垫,超时就好理解了,cancel 是手动取消,超时是自动取消,只要起一个定时的协程,到时间后执行 cancel 即可。设置超时时间有2种方式:context.WithTimeoutcontext.WithDeadline,WithTimeout 是设置一段时间后,WithDeadline 是设置一个截止时间点,WithTimeout 最终也会转换为 WithDeadline。

//来自src/context/example_test.go
funcExampleWithTimeout(){
	//Passacontextwithatimeouttotellablockingfunctionthatit
	//shouldabandonitsworkafterthetimeoutelapses.
	ctx,cancel:=context.WithTimeout(context.Background(),shortDuration)
	defercancel()

	select{
	case

基于 Context 可以设置任意代码段执行的超时机制,就可以设计一种脱离操作系统能力的请求超时能力。超时机制简介看一下 Go 的 HttpClient 超时配置说明:

	client:=http.Client{
		Timeout:10*time.Second,
	}
	
	//来自src/net/http/client.go
	typeClientstruct{
	//...省略其他字段
	//Timeoutspecifiesatimelimitforrequestsmadebythis
	//Client.Thetimeoutincludesconnectiontime,any
	//redirects,andreadingtheresponsebody.Thetimerremains
	//runningafterGet,Head,Post,orDoreturnandwill
	//interruptreadingoftheResponse.Body.
	//
	//ATimeoutofzeromeansnotimeout.
	//
	//TheClientcancelsrequeststotheunderlyingTransport
	//asiftheRequest'sContextended.
	//
	//Forcompatibility,theClientwillalsousethedeprecated
	//CancelRequestmethodonTransportiffound.New
	//RoundTripperimplementationsshouldusetheRequest'sContext
	//forcancellationinsteadofimplementingCancelRequest.
	Timeouttime.Duration
}

翻译一下注释:Timeout 包括了连接、redirect、读取数据的时间,定时器会在 Timeout 时间后打断数据的读取,设为0则没有超时限制。

翻译一下注释:Timeout 包括了连接、redirect、读取数据的时间,定时器会在 Timeout 时间后打断数据的读取,设为0则没有超时限制。
也就是说这个超时是一个请求的总体超时时间,而不必再分别去设置连接超时、读取超时等等。这对于使用者来说可能是一个更好的选择,大部分场景,使用者不必关心到底是哪部分导致的超时,而只是想这个 HTTP 请求整体什么时候能返回。超时机制底层原理以一个最简单的例子来阐述超时机制的底层原理。这里我起了一个本地服务,用 Go HttpClient 去请求,超时时间设置为 10 分钟,建议使 Debug 时设置长一点,否则可能超时导致无法走完全流程。

	client:=http.Client{
		Timeout:10*time.Minute,
	}
	resp,err:=client.Get("http://127.0.0.1:81/hello")

//来自src/net/http/client.go
deadline=c.deadline()

//来自src/net/http/client.go
stopTimer,didTimeout:=setRequestCancel(req,rt,deadline)

登录后复制

登录后复制这里返回的 stopTimer 就是可以手动 cancel 的方法,didTimeout 是判断是否超时的方法。这两个可以理解为回调方法,调用 stopTimer() 可以手动 cancel,调用 didTimeout() 可以返回是否超时。设置的主要代码其实就是将请求的 Context 替换为 cancelCtx,后续所有的操作都将携带这个 cancelCtx:

//来自src/net/http/client.go
varcancelCtxfunc()
ifoldCtx:=req.Context();timeBeforeContextDeadline(deadline,oldCtx){
	req.ctx,cancelCtx=context.WithDeadline(oldCtx,deadline)
}

同时,再起一个定时器,当超时时间到了之后,将 timedOut 设置为 true,再调用 doCancel(),doCancel() 是调用真正 RoundTripper (代表一个 HTTP 请求事务)的 CancelRequest,也就是取消请求,这个跟实现有关。

//来自src/net/http/client.go
timer:=time.NewTimer(time.Until(deadline))
vartimedOutatomicBool

gofunc(){
	select{
	case

Go 默认 RoundTripper CancelRequest 实现是关闭这个连接

//位于src/net/http/transport.go
//CancelRequestcancelsanin-flightrequestbyclosingitsconnection.
//CancelRequestshouldonlybecalledafterRoundTriphasreturned.
func(t*Transport)CancelRequest(req*Request){
	t.cancelRequest(cancelKey{req},errRequestCanceled)
}

同时,再起一个定时器,当超时时间到了之后,将 timedOut 设置为 true,再调用 doCancel(),doCancel() 是调用真正 RoundTripper (代表一个 HTTP 请求事务)的 CancelRequest,也就是取消请求,这个跟实现有关。Go 默认 RoundTripper CancelRequest 实现是关闭这个连接

//位于src/net/http/transport.go
for{
	select{
	case

代码的开头监听 ctx.Done,如果超时则直接返回,使用 for 循环主要是为了请求的重试。

代码的开头监听 ctx.Done,如果超时则直接返回,使用 for 循环主要是为了请求的重试。
后续的 getConn 是阻塞的,代码比较长,挑重点说,先看看有没有空闲连接,如果有则直接返回

//位于src/net/http/transport.go
//Queueforidleconnection.
ifdelivered:=t.queueForIdleConn(w);delivered{
	//...
	returnpc,nil
}

如果没有空闲连接,起个协程去异步建立,建立成功再通知主协程

//位于src/net/http/transport.go
//Queueforpermissiontodial.
t.queueForDial(w)

再接着是一个 select 等待连接建立成功、超时或者主动取消,这就实现了在连接过程中的超时

//位于src/net/http/transport.go
//Waitforcompletionorcancellation.
select{
case

如果没有空闲连接,起个协程去异步建立,建立成功再通知主协程
再接着是一个 select 等待连接建立成功、超时或者主动取消,这就实现了在连接过程中的超时
在上一条连接建立的时候,每个链接还偷偷起了两个协程,一个负责往连接中写入数据,另一个负责读数据,他们都监听了相应的 channel。

//位于src/net/http/transport.go
gopconn.readLoop()
gopconn.writeLoop()

其中 wirteLoop 监听来自主协程的数据,并往连接中写入

//位于src/net/http/transport.go
func(pc*persistConn)writeLoop(){
	deferclose(pc.writeLoopDone)
	for{
		select{
		casewr:=

同理,readLoop 读取响应数据,并写回主协程。读与写的过程中如果超时了,连接将被关闭,报错退出。超时机制小结Go 的这种请求超时机制,可随时终止请求,可设置整个请求的超时时间。其实现主要依赖协程、channel、select 机制的配合。总结出套路是:主协程生成 cancelCtx,传递给子协程,主协程与子协程之间用 channel 通信主协程 select channel 和 cancelCtx.Done,子协程完成或取消则 return循环任务:子协程起一个循环处理,每次循环开始都 select cancelCtx.Done,如果完成或取消则退出阻塞任务:子协程 select 阻塞任务与 cancelCtx.Done,阻塞任务处理完或取消则退出以循环任务为例Java 能实现这种超时机制吗直接说结论:暂时不行。首先 Java 的线程太重,像 Go 这样一次请求开了这么多协程,换成线程性能会大打折扣。其次 Go 的 channel 虽然和 Java 的阻塞队列类似,但 Go 的 select 是多路复用机制,Java 暂时无法实现,即无法监听多个队列是否有数据到达。所以综合来看 Java 暂时无法实现类似机制。到此,相信大家对“Golang自带的HttpClient超时机制怎么实现”有了更深的了解,不妨来实际操作一番吧!这里是百云主机网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

相关推荐: 怎么使用vite+vue3.0+ts+element-plus搭建项目

这篇文章主要介绍“怎么使用vite+vue3.0+ts+element-plus搭建项目”,在日常操作中,相信很多人在怎么使用vite+vue3.0+ts+element-plus搭建项目问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家…

免责声明:本站发布的图片视频文字,以转载和分享为主,文章观点不代表本站立场,本站不承担相关法律责任;如果涉及侵权请联系邮箱:360163164@qq.com举报,并提供相关证据,经查实将立刻删除涉嫌侵权内容。

(0)
打赏 微信扫一扫 微信扫一扫
上一篇 02/23 11:39
下一篇 02/23 11:39

相关推荐