别设置 Transport.Dail里的SetDeadline
http.Transport.Dial的配置里有个SetDeadline,它表示连接建立后发送接收数据的超时时间。听起来跟client.Timeout
很像。
那么他们有什么区别呢?我们通过一个例子去看下。
package main import ( "bytes" "encoding/json" "fmt" "io/ioutil" "net" "net/http" "time" ) var tr *http.Transport func init() { tr = &http.Transport{ MaxIdleConns: 100, Dial: func(netw, addr string) (net.Conn, error) { conn, err := net.DialTimeout(netw, addr, time.Second*2) //设置建立连接超时 if err != nil { return nil, err } err = conn.SetDeadline(time.Now().Add(time.Second * 3)) //设置发送接受数据超时 if err != nil { return nil, err } return conn, nil }, } } func main() { for { _, err := Get("http://www.baidu.com/") if err != nil { fmt.Println(err) break } } } func Get(url string) ([]byte, error) { m := make(map[string]interface{}) data, err := json.Marshal(m) if err != nil { return nil, err } body := bytes.NewReader(data) req, _ := http.NewRequest("Get", url, body) req.Header.Add("content-type", "application/json") client := &http.Client{ Transport: tr, } res, err := client.Do(req) if res != nil { defer res.Body.Close() } if err != nil { return nil, err } resBody, err := ioutil.ReadAll(res.Body) if err != nil { return nil, err } return resBody, nil }
上面这段代码,我们设置了SetDeadline为3s,当你执行一段时间,会发现请求baidu会超时,但其实baidu的接口很快,不可能超过3s。
在生产环境中,假如是你的服务调用下游服务,你看到的现象就是,你的服务显示3s超时了,但下游服务可能只花了200ms就已经响应你的请求了,并且这是随机发生的问题。遇到这种情况,我们一般会认为是“网络波动”。
但如果我们去对网络抓包,就很容易发现问题的原因 。
抓包结果
可以看到,在tcp三次握手之后,就会开始多次网络请求。直到3s的时候,就会触发RST包,断开连接。也就是说,我们设置的SetDeadline,并不是指单次http请求的超时是3s,而是指整个tcp连接的存活时间是3s,计算长连接被连接池回收,这个时间也不会重置。
SetDeadline的解释
我实在想不到什么样的场景会需要这个功能,因此我的建议是,不要使用它。
下面是修改后的代码。这个问题其实在我另外一篇文章有过详细的解释,如果你对源码解析感兴趣的话,可以去看看。
package main import ( "bytes" "encoding/json" "fmt" "io/ioutil" "net/http" "time" ) var tr *http.Transport func init() { tr = &http.Transport{ MaxIdleConns: 100, // 下面的代码被干掉了 //Dial: func(netw, addr string) (net.Conn, error) { // conn, err := net.DialTimeout(netw, addr, time.Second*2) //设置建立连接超时 // if err != nil { // return nil, err // } // err = conn.SetDeadline(time.Now().Add(time.Second * 3)) //设置发送接受数据超时 // if err != nil { // return nil, err // } // return conn, nil //}, } } func Get(url string) ([]byte, error) { m := make(map[string]interface{}) data, err := json.Marshal(m) if err != nil { return nil, err } body := bytes.NewReader(data) req, _ := http.NewRequest("Get", url, body) req.Header.Add("content-type", "application/json") client := &http.Client{ Transport: tr, Timeout: 3*time.Second, // 超时加在这里,是每次调用的超时 } res, err := client.Do(req) if res != nil { defer res.Body.Close() } if err != nil { return nil, err } resBody, err := ioutil.ReadAll(res.Body) if err != nil { return nil, err } return resBody, nil } func main() { for { _, err := Get("http://www.baidu.com/") if err != nil { fmt.Println(err) break } } }
总结
golang的net/http部分有不少细节点,直接上源码分析怕劝退不少人,所以希望以几个例子作为引子展开话题然后深入了解它的内部实现。总体内容比较碎片化,但这个库的重点知识点基本都在这里面了。希望对大家后续排查问题有帮助。
最后
离开广东好长时间了,好久没人叫我靓仔了。
大家可以在评论区里,叫我一靓仔吗?
我这么善良质朴的愿望,能被满足吗?
如果实在叫不出口的话,能帮我点下右下角的点赞和在看吗?