go net/http Client 使用——长连接客户端的使用

hardecz 2017-12-06 15:46:40 
17705 

收藏  3
最后发布: 2017-12-06 15:46:40 首次发布: 2017-12-06 15:46:40
go net/http Client 使用总结
Client 数据结构
`2. // A Client is an HTTP client. Its zero value (DefaultClient) is a3. // usable client that uses DefaultTransport.4. //5. // The Client's Transport typically has internal state (cached TCP6. // connections), so Clients should be reused instead of created as7. // needed. Clients are safe for concurrent use by multiple goroutines.8. //9. // A Client is higher-level than a RoundTripper (such as Transport)10. // and additionally handles HTTP details such as cookies and11. // redirects.12. type Client struct {13. // Transport specifies the mechanism by which individual14. // HTTP requests are made.15. // If nil, DefaultTransport is used.16. Transport RoundTripper18. // CheckRedirect specifies the policy for handling redirects.19. // If CheckRedirect is not nil, the client calls it before20. // following an HTTP redirect. The arguments req and via are21. // the upcoming request and the requests made already, oldest22. // first. If CheckRedirect returns an error, the Client's Get23. // method returns both the previous Response (with its Body24. // closed) and CheckRedirect's error (wrapped in a url.Error)25. // instead of issuing the Request req.26. // As a special case, if CheckRedirect returns ErrUseLastResponse,27. // then the most recent response is returned with its body28. // unclosed, along with a nil error.29. //30. // If CheckRedirect is nil, the Client uses its default policy,31. // which is to stop after 10 consecutive requests.32. CheckRedirect func(req *<span class="" "="">Request, via []*Request) error34. // Jar specifies the cookie jar.35. // If Jar is nil, cookies are not sent in requests and ignored36. // in responses.37. Jar CookieJar39. // Timeout specifies a time limit for requests made by this40. // Client. The timeout includes connection time, any41. // redirects, and reading the response body. The timer remains42. // running after Get, Head, Post, or Do return and will43. // interrupt reading of the Response.Body.44. //45. // A Timeout of zero means no timeout.46. //47. // The Client cancels requests to the underlying Transport48. // using the Request.Cancel mechanism. Requests passed50. ay still set Request.Cancel; both will51. // cancel the request.52. //53. // For compatibility, the Client will also use the deprecated54. // CancelRequest method on Transport if found. New55. // RoundTripper implementations should use Request.Cancel56. // instead of implementing CancelRequest.57. Timeout time.Duration58. }`
注意Transport数据结构,对应如下:
`1. // Transport is an implementation of RoundTripper that supports HTTP,2. // HTTPS, and HTTP proxies (for either HTTP or HTTPS with CONNECT).3. //4. // By default, Transport caches connections for future re-use.5. // This may leave many open connections when accessing many hosts.6. // This behavior can be managed using Transport's CloseIdleConnections method7. // and the MaxIdleConnsPerHost and DisableKeepAlives fields.8. //9. // Transports should be reused instead of created as needed.10. // Transports are safe for concurrent use by multiple goroutines.11. //12. // A Transport is a low-level primitive for making HTTP and HTTPS requests.13. // For high-level functionality, such as cookies and redirects, see Client.14. //15. // Transport uses HTTP/1.1 for HTTP URLs and either HTTP/1.1 or HTTP/216. // for HTTPS URLs, depending on whether the server supports HTTP/2.17. // See the package docs for more about HTTP/2.18. type Transport struct {19. idleMu sync.Mutex20. wantIdle bool // user has requested to close all idle conns21. idleConn map[connectMethodKey][]*persistConn // most recently used at end22. idleConnCh map[connectMethodKey]chan *persistConn23. idleLRU connLRU25. reqMu sync.Mutex26. reqCanceler map[*Request]func()28. altMu sync.RWMutex29. altProto map[string]RoundTripper // nil or map of URI scheme => RoundTripper31. // Proxy specifies a function to return a proxy for a given32. // Request. If the function returns a non-nil error, the33. // request is aborted with the provided error.34. // If Proxy is nil or returns a nil *URL, no proxy is used.35. Proxy func(*Request) (*url.URL, error)37. // DialContext specifies the dial function for creating unencrypted TCP connections.38. // If DialContext is nil (and the deprecated Dial below is also nil),39. // then the transport dials using package net.40. DialContext func(ctx context.Context, network, addr string) (net.Conn, error)42. // Dial specifies the dial function for creating unencrypted TCP connections.43. //44. // Deprecated: Use DialContext instead, which allows the transport45. // to cancel dials as soon as they are no longer needed.46. // If both are set, DialContext takes priority.47. Dial func(network, addr string) (net.Conn, error)49. // DialTLS specifies an optional dial function for creating50. // TLS connections for non-proxied HTTPS requests.51. //52. // If DialTLS is nil, Dial and TLSClientConfig are used.53. //54. // If DialTLS is set, the Dial hook is not used for HTTPS55. // requests and the TLSClientConfig and TLSHandshakeTimeout56. // are ignored. The returned net.Conn is assumed to already be57. // past the TLS handshake.58. DialTLS func(network, addr string) (net.Conn, error)60. // TLSClientConfig specifies the TLS configuration to use with61. // tls.Client. If nil, the default configuration is used.62. TLSClientConfig *tls.Config64. // TLSHandshakeTimeout specifies the maximum amount of time waiting to65. // wait for a TLS handshake. Zero means no timeout.66. TLSHandshakeTimeout time.Duration68. // DisableKeepAlives, if true, prevents re-use of TCP connections69. // between different HTTP requests.70. DisableKeepAlives bool72. // DisableCompression, if true, prevents the Transport from73. // requesting compression with an "Accept-Encoding: gzip"74. // request header when the Request contains no existing75. // Accept-Encoding value. If the Transport requests gzip on76. // its own and gets a gzipped response, it's transparently77. // decoded in the Response.Body. However, if the user78. // explicitly requested gzip it is not automatically79. // uncompressed.80. DisableCompression bool82. // MaxIdleConns controls the maximum number of idle (keep-alive)83. // connections across all hosts. Zero means no limit.84. MaxIdleConns int86. // MaxIdleConnsPerHost, if non-zero, controls the maximum idle87. // (keep-alive) connections to keep per-host. If zero,88. // DefaultMaxIdleConnsPerHost is used.89. MaxIdleConnsPerHost int91. // IdleConnTimeout is the maximum amount of time an idle92. // (keep-alive) connection will remain idle before closing93. // itself.94. // Zero means no limit.95. IdleConnTimeout time.Duration97. // ResponseHeaderTimeout, if non-zero, specifies the amount of98. // time to wait for a server's response headers after fully99. // writing the request (including its body, if any). This100. // time does not include the time to read the response body.101. ResponseHeaderTimeout time.Duration103. // ExpectContinueTimeout, if non-zero, specifies the amount of104. // time to wait for a server's first response headers after fully105. // writing the request headers if the request has an106. // "Expect: 100-continue" header. Zero means no timeout.107. // This time does not include the time to send the request header.108. ExpectContinueTimeout time.Duration110. // TLSNextProto specifies how the Transport switches to an111. // alternate protocol (such as HTTP/2) after a TLS NPN/ALPN112. // protocol negotiation. If Transport dials an TLS connection113. // with a non-empty protocol name and TLSNextProto contains a114. // map entry for that key (such as "h2"), then the func is115. // called with the request's authority (such as "example.com"116. // or "example.com:1234") and the TLS connection. The function117. // must return a RoundTripper that then handles the request.118. // If TLSNextProto is nil, HTTP/2 support is enabled automatically.119. TLSNextProto map[string]func(authority string, c *tls.Conn) RoundTripper121. // MaxResponseHeaderBytes specifies a limit on how many122. // response bytes are allowed in the server's response123. // header.124. //125. // Zero means to use a default limit.126. MaxResponseHeaderBytes int64128. // nextProtoOnce guards initialization of TLSNextProto and129. // h2transport (via onceSetNextProtoDefaults)130. nextProtoOnce sync.Once131. h2transport *http2Transport // non-nil if http2 wired up133. // TODO: tunable on max per-host TCP dials in flight (Issue 13957)134. }`
实际上是一个连接池,所以在使用的过程中通常是初始化一个Transport和Client,在每个处理函数中都共用该连接池,例如如下 Demo:
`1. package main3. import (4. "bytes"5. "io/ioutil"6. "log"7. "net"8. "net/http"9. "time"10. )12. var (13. httpClient *http.Client14. )16. // init HTTPClient17. func init() {18. httpClient = createHTTPClient()19. }21. const (22. MaxIdleConns int = 10023. MaxIdleConnsPerHost int = 10024. IdleConnTimeout int = 9025. )27. // createHTTPClient for connection re-use28. func createHTTPClient() *http.Client {29. client := &http.Client{30. Transport: &http.Transport{31. Proxy: http.ProxyFromEnvironment,32. DialContext: (&net.Dialer{33. Timeout: 30 * time.Second,34. KeepAlive: 30 * time.Second,35. }).DialContext,36. MaxIdleConns: MaxIdleConns,37. MaxIdleConnsPerHost: MaxIdleConnsPerHost,38. IdleConnTimeout: time.Duration(_IdleConnTimeout_)* time.Second,39. },`
Timeout: 20 \* time._Second_,
`1. }2. return client3. }5. func main() {6. var endPoint string = "https://localhost:8080/doSomething"8. req, err := http.NewRequest("POST", endPoint, bytes.NewBuffer([]byte("Post this data")))9. if err != nil {10. log.Fatalf("Error Occured. %+v", err)11. }12. req.Header.Set("Content-Type", "application/x-www-form-urlencoded")14. // use httpClient to send request15. response, err := httpClient.Do(req)16. if err != nil && response == nil {17. log.Fatalf("Error sending request to API endpoint. %+v", err)18. } else {19. // Close the connection to reuse it20. defer response.Body.Close()22. // Let's check if the work actually is done23. // We have seen inconsistencies even when we get 200 OK response24. body, err := ioutil.ReadAll(response.Body)25. if err != nil {26. log.Fatalf("Couldn't parse response body. %+v", err)27. }29. log.Println("Response Body:", string(body))30. }32. }`
下面详细讲解Transport结构需要注意的几个参数设置:
- DisableKeepAlives
 
`1. // DisableKeepAlives, if true, prevents re-use of TCP connections2. // between different HTTP requests.`
表示是否开启 http keepalive 功能,也即是否重用连接,默认开启 (false)
- MaxIdleConns
 
`1. // MaxIdleConns controls the maximum number of idle (keep-alive)2. // connections across all hosts. Zero means no limit.`
表示连接池对所有 host 的最大链接数量,host 也即 dest-ip,默认为无穷大(0),但是通常情况下为了性能考虑都要严格限制该数目(实际使用中通常利用压测 二分得到该参数的最佳近似值)。太大容易导致客户端和服务端的 socket 数量剧增,导致内存吃满,文件描述符不足等问题;太小则限制了连接池的 socket 数量,资源利用率较低。
- MaxIdleConnsPerHost
 
`1. // MaxIdleConnsPerHost, if non-zero, controls the maximum idle2. // (keep-alive) connections to keep per-host. If zero,3. // DefaultMaxIdleConnsPerHost is used.`
表示连接池对每个 host 的最大链接数量,从字面意思也可以看出:
`MaxIdleConnsPerHost <= MaxIdleConns`
如果客户端只需要访问一个 host,那么最好将MaxIdleConnsPerHost与MaxIdleConns设置为相同,这样逻辑更加清晰。
- IdleConnTimeout
 
`1. // IdleConnTimeout is the maximum amount of time an idle2. // (keep-alive) connection will remain idle before closing3. // itself.4. // Zero means no limit.`
空闲 timeout 设置,也即 socket 在该时间内没有交互则自动关闭连接(注意:该 timeout 起点是从每次空闲开始计时,若有交互则重置为 0), 该参数通常设置为分钟级别,例如:90 秒。
- DialContext
 
`1. // DialContext specifies the dial function for creating unencrypted TCP connections.2. // If DialContext is nil (and the deprecated Dial below is also nil),3. // then the transport dials using package net.4. DialContext func(ctx context.Context, network, addr string) (net.Conn, error)`
该函数用于创建 http(非 https)连接,通常需要关注Timeout和KeepAlive参数。前者表示建立 Tcp 链接超时时间;后者表示底层为了维持 http keepalive 状态 每隔多长时间发送 Keep-Alive 报文。Timeout通常设置为 30s(网络环境良好),KeepAlive通常设置为 30s(与 IdleConnTimeout 要对应)。
补充说明
Transport数据结构中的MaxConnsPerHost参数目前正在研发中(Go 项目),其表示对每个 host 可以发出的最大连接个数(包括长链接和短链接),是非常有用的参数,可以用来对 socket 数量进行限制,配合MaxIdleConns和MaxIdleConnsPerHost一起使用。
net/http Transport MaxConnsPerHost
