seo有哪些网站,2021年热门手游推荐,wordpress展览会,云小店自助下单大家好#xff0c;我是蓝胖子#xff0c;说起提高http的传输效率#xff0c;很多人会开启http的Keep-Alive选项#xff0c;这会http请求能够复用tcp连接#xff0c;节省了握手的开销。但开启Keep-Alive真的没有问题吗#xff1f;我们来细细分析下。
最大空闲时间造成请求…大家好我是蓝胖子说起提高http的传输效率很多人会开启http的Keep-Alive选项这会http请求能够复用tcp连接节省了握手的开销。但开启Keep-Alive真的没有问题吗我们来细细分析下。
最大空闲时间造成请求失败
通常我们开启Keep-Alive后 服务端还会设置连接的最大空闲时间这样能保证在没有请求发生时及时释放连接不会让过多的tcp连接白白占用机器资源。
问题就出现在服务端主动关闭空闲连接这个地方试想一下这个场景客户端复用了一个空闲连接发送http请求但此时服务端正好检测到这个连接超过了配置的连接最大空闲时间在请求到达前提前关闭了空闲连接这样就会导致客户端此次的请求失败。
过程如下图所示 如何避免此类问题
上述问题在理论上的确是一直存在的但是我们可以针对发送http请求的代码做一些加强来尽量避免此类问题。来看看在Golang中http client客户端是如何尽量做到安全的http重试的。
go http client 是如何做到安全重试请求的
在golang中在发送一次http请求后如果发现请求失败会通过shouldRetryRequest 函数判断此次请求是否应该被重试代码如下
func (pc *persistConn) shouldRetryRequest(req *Request, err error) bool { if http2isNoCachedConnError(err) { // Issue 16582: if the user started a bunch of // requests at once, they can all pick the same conn // and violate the servers max concurrent streams. // Instead, match the HTTP/1 behavior for now and dial // again to get a new TCP connection, rather than failing // this request. return true } if err errMissingHost { // User error. return false } if !pc.isReused() { // This was a fresh connection. Theres no reason the server // shouldve hung up on us. // // Also, if we retried now, we could loop forever // creating new connections and retrying if the server // is just hanging up on us because it doesnt like // our request (as opposed to sending an error). return false } if _, ok : err.(nothingWrittenError); ok { // We never wrote anything, so its safe to retry, if theres no body or we // can rewind the body with GetBody. return req.outgoingLength() 0 || req.GetBody ! nil } if !req.isReplayable() { // Dont retry non-idempotent requests. return false } if _, ok : err.(transportReadFromServerError); ok { // We got some non-EOF net.Conn.Read failure reading // the 1st response byte from the server. return true } if err errServerClosedIdle { // The server replied with io.EOF while we were trying to // read the response. Probably an unfortunately keep-alive // timeout, just as the client was writing a request. return true } return false // conservatively
}我们来挨个看看每个判断逻辑
http2isNoCachedConnError 是关于http2的判断逻辑这部分逻辑我们先不管。
err errMissingHost 这是由于请求路径中缺少请求的域名或ip信息这种情况不需要重试。
pc.isReused() 这个是在判断此次请求的连接是不是属于连接复用情况因为如果是新创建的连接服务器正常情况下是没有理由拒绝我们的请求此时如果请求失败了则新建连接就好不需要重试。
if _, ok : err.(nothingWrittenError); ok 这是在判断此次的请求失败的时候是不是还没有向对端服务器写入任何字节如果没有写入任何字节并且请求的body是空的或者有body但是能通过req.GetBody 恢复body就能进行重试。 注意因为在真正向连接写入请求头和body时golang其实是构建了一个bufio.Writer 去封装了连接对象数据是先写到了bufio.Writer 缓冲区中所以有可能出现请求体Request已经读取了部分body写入到缓冲区中但实际真正向连接写入数据时失败的场景这种情况重试就需要恢复原先的body重试请求时从头读取body数据。 req.isReplayable() 则是从请求体中判断这个请求是否能够被重试如果不满足重试要求则直接不重试满足重试要求则会继续进行下面的重试判断。 其代码如下如果http的请求body为空或者有GetBody 方法能为其恢复body并且是GET, “HEAD”, “OPTIONS”, “TRACE” 方法之一则认为该请求重试是安全的。
还有种情况是如果http请求头中有Idempotency-Key 或者X-Idempotency-Key 也认为重试是安全的。 X-Idempotency-Key 和 Idempotency-Key 其实是为了给post请求的重试给了一个后门对应的key是由业务方自己定义的具有幂等性质的key服务端可以拿到它做幂等性校验所以重试是安全的。 func (r *Request) isReplayable() bool { if r.Body nil || r.Body NoBody || r.GetBody ! nil { switch valueOrDefault(r.Method, GET) { case GET, HEAD, OPTIONS, TRACE: return true } // The Idempotency-Key, while non-standard, is widely used to // mean a POST or other request is idempotent. See // https://golang.org/issue/19943#issuecomment-421092421 if r.Header.has(Idempotency-Key) || r.Header.has(X-Idempotency-Key) { return true } } return false
}只有认为请求重试是安全后才会进一步判断请求失败 是不是由于服务端关闭空闲连接造成的 _, ok : err.(transportReadFromServerError) 和 errServerClosedIdle都是由于服务端关闭空闲连接造成的错误码如果产生的错误码是其中之一则都是允许被重试的。 所以综上你可以看出如果你发的请求是一个不带有Idempotency-Key或者X-Idempotency-Keypost请求头的post请求那么即使是由于服务器关闭空闲连接造成请求失败该post请求是不会被重试的。不过在其他请求方法比如GET方法下由服务器关闭空闲连接造成的请求错误Golang 能自动重试。 最佳实践
针对上述场景我们应该如何设计我们的请求发送来保证安全可靠的发送http请求呢针对于Golang开发环境我总结几点经验
1GET请求可以自动重试如果你的接口没有完全准寻restful 风格GET请求的处理方法仍然有修改数据的操作那么你应该保证你的接口是幂等的。
2POST请求不会自动重试但是如果你需要让你的操作百分百的成功请添加失败重试逻辑同样服务端最好做好幂等操作。
3如果对性能要求不是那么高那么直接关闭掉http的长链接将请求头的Connection 字段设置为close 这样每次发送发送http请求时都是用的新的连接不会存在潜在的服务端关闭空闲连接造成请求失败的问题。
4第四点其实你可以发现网络请求不管你的网络情况是否好坏都是存在失败的可能即使将http长连接关掉在网络坏的情况下请求还是会失败失败了要想保证成功就得重试重试就一定得保证服务端接口幂等了所以你的接口如果是幂等的你的请求如果具有重试逻辑那么恭喜你你的系统十分可靠。
5最后一点千万不要抱着侥幸心理去看待网络请求正如第四点说的那样不管你的网络情况是否好坏都是存在失败的可能。嗯面对异常编程。