超时原因
大家知道 HTTP 是应用层协议,传输层用的是 TCP 协议。
HTTP 协议从 1.0 以前,默认用的是短连接,每次发起请求都会建立 TCP 连接。收发数据。然后断开连接。
TCP 连接每次都是三次握手。每次断开都要四次挥手。
其实没必要每次都建立新连接,建立的连接不断开就好了,每次发送数据都复用就好了。
于是乎,HTTP 协议从 1.1 之后就默认使用长连接。具体相关信息可以看之前的 这篇文章 。
那么 golang 标准库里也兼容这种实现。
通过建立一个连接池,针对每个域名建立一个 TCP 长连接,比如 http://baidu.com 和 http://golang.com 就是两个不同的域名。
第一次访问 http://baidu.com 域名的时候会建立一个连接,用完之后放到空闲连接池里,下次再要访问 http://baidu.com 的时候会重新从连接池里把这个连接捞出来复用。
复用长连接
为什么要强调是同一个域名:一个域名会建立一个连接,一个连接对应一个读 goroutine 和一个写 goroutine。正因为是同一个域名,所以最后才会泄漏 3 个 goroutine,如果不同域名的话,那就会泄漏 1+2*N 个协程,N 就是域名数。
假设第一次请求要 100ms,每次请求完 http://baidu.com 后都放入连接池中,下次继续复用,重复 29 次,耗时 2900ms。
第 30 次请求的时候,连接从建立开始到服务返回前就已经用了 3000ms,刚好到设置的 3s 超时阈值,那么此时客户端就会报超时 i/o timeout。
虽然这时候服务端其实才花了 100ms,但耐不住前面 29 次加起来的耗时已经很长。
也就是说只要通过 http.Transport
设置了 err = conn.SetDeadline(time.Now().Add(time.Second * 3))
,并且用了长连接,哪怕服务端处理再快,客户端设置的超时再长,总有一刻,程序会报超时错误。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论