2013-07-30 49 views
49

Tôi hiện đang vật lộn để tìm cách tái sử dụng các kết nối khi tạo các bài đăng HTTP trong Golang.Sử dụng lại các kết nối http trong Golang

tôi đã tạo ra một phương tiện giao thông và khách hàng như sau:

// Create a new transport and HTTP client 
tr := &http.Transport{} 
client := &http.Client{Transport: tr} 

tôi sau đó đi qua con trỏ client này vào một goroutine mà là làm cho nhiều bài viết để các thiết bị đầu cuối tương tự như vậy:

r, err := client.Post(url, "application/json", post) 

Nhìn vào netstat điều này dường như dẫn đến kết nối mới cho mỗi bài viết dẫn đến một số lượng lớn các kết nối đồng thời đang được mở.

Cách chính xác để sử dụng lại kết nối trong trường hợp này là gì?

Trả lời

8

IIRC, máy khách mặc định hiện kết nối tái sử dụng. Bạn có đang đóng response không?

Người gọi phải đóng lại. Khi đọc xong. Nếu resp.Body không được đóng, RoundTripper bên dưới của Client (thường là Transport) có thể không thể sử dụng lại kết nối TCP liên tục đến máy chủ để yêu cầu tiếp tục "tiếp tục".

+0

Xin chào, cảm ơn bạn đã phản hồi. Vâng, xin lỗi tôi nên cũng đã bao gồm điều đó. Tôi đang đóng kết nối với r.Body.Close(). – sicr

+0

@sicr, bạn có tích cực là máy chủ không thực sự tự đóng các kết nối không? Ý tôi là, những kết nối nổi bật này có thể ở một trong các trạng thái '* _WAIT' hoặc một cái gì đó như thế này – kostix

+1

@kostix Tôi thấy một số lượng lớn các kết nối với trạng thái được thành lập khi nhìn vào netstat. Có vẻ như một kết nối mới đang được sinh ra trên mọi yêu cầu POST như trái ngược với cùng một kết nối đang được sử dụng lại. – sicr

61

Bạn phải đảm bảo rằng bạn đọc cho đến khi phản hồi hoàn tất trước khi gọi Close().

ví dụ:

res, _ := client.Do(req) 
io.Copy(ioutil.Discard, res.Body) 
res.Body.Close() 

Để đảm bảo http.Client tái sử dụng kết nối hãy chắc chắn để làm hai việc:

  • đọc cho đến khi đáp ứng được hoàn tất (tức ioutil.ReadAll(resp.Body))
  • Gọi Body.Close()
+1

Tôi đang đăng lên cùng một máy chủ. Tuy nhiên, sự hiểu biết của tôi là MaxIdleConnsPerHost sẽ dẫn đến kết nối nhàn rỗi đang bị đóng. đây không phải là trường hợp à? – sicr

+0

Nó sẽ cố gắng tái sử dụng các kết nối nhàn rỗi, nhưng tôi nghĩ đây không phải là vấn đề của bạn, vì nó có thể tái sử dụng các kết nối TCP. Bạn có thêm tiêu đề Kết nối: tiếp tục hoạt động vào Giao thông không? –

+3

+1, bởi vì tôi gọi là 'trì hoãn res.Body.Close()' trong một chương trình tương tự, nhưng cuối cùng trở lại từ hàm thỉnh thoảng trước khi phần đó được thực hiện (ví dụ: 'resp.StatusCode! = 200'), còn lại ** rất nhiều ** của các mô tả tập tin mở nhàn rỗi và cuối cùng đã giết chết chương trình của tôi. Nhét sợi chỉ này làm tôi xem lại phần mã và facepalm đó. cảm ơn. – sa125

20

Edit: Đây là chi tiết của một lưu ý cho những người xây dựng một Transport và Client cho mọi yêu cầu.

Transport là struct chứa các kết nối để tái sử dụng:

http://golang.org/src/pkg/net/http/transport.go#L46

Vì vậy, nếu bạn tạo một Giao thông vận tải mới cho mỗi yêu cầu, nó sẽ tạo ra các kết nối mới mỗi lần. Trong trường hợp này, giải pháp là chia sẻ một cá thể Transport giữa các máy khách.

22

Nếu có ai vẫn đang tìm câu trả lời về cách thực hiện, đây là cách tôi đang thực hiện.

package main 

import (
    "bytes" 
    "io/ioutil" 
    "log" 
    "net/http" 
    "time" 
) 

var (
    httpClient *http.Client 
) 

const (
    MaxIdleConnections int = 20 
    RequestTimeout  int = 5 
) 

// init HTTPClient 
func init() { 
    httpClient = createHTTPClient() 
} 

// createHTTPClient for connection re-use 
func createHTTPClient() *http.Client { 
    client := &http.Client{ 
     Transport: &http.Transport{ 
      MaxIdleConnsPerHost: MaxIdleConnections, 
     }, 
     Timeout: time.Duration(RequestTimeout) * time.Second, 
    } 

    return client 
} 

func main() { 
    var endPoint string = "https://localhost:8080/doSomething" 

    req, err := http.NewRequest("POST", endPoint, bytes.NewBuffer([]byte("Post this data"))) 
    if err != nil { 
     log.Fatalf("Error Occured. %+v", err) 
    } 
    req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 

    // use httpClient to send request 
    response, err := httpClient.Do(req) 
    if err != nil && response == nil { 
     log.Fatalf("Error sending request to API endpoint. %+v", err) 
    } else { 
     // Close the connection to reuse it 
     defer response.Body.Close() 

     // Let's check if the work actually is done 
     // We have seen inconsistencies even when we get 200 OK response 
     body, err := ioutil.ReadAll(response.Body) 
     if err != nil { 
      log.Fatalf("Couldn't parse response body. %+v", err) 
     } 

     log.Println("Response Body:", string(body)) 
    } 

} 

Go Playground: http://play.golang.org/p/oliqHLmzSX

Nói tóm lại, tôi đang tạo ra một phương pháp khác nhau để tạo ra một khách hàng HTTP và gán nó vào biến toàn cầu và sau đó sử dụng nó để thực hiện yêu cầu. Lưu ý rằng

defer response.Body.Close() 

Thao tác này sẽ đóng kết nối và đặt sẵn sàng để sử dụng lại lần nữa.

Hy vọng điều này sẽ giúp ai đó.

+0

Điều này làm việc cho tôi, cảm ơn! –

+0

Đang sử dụng http.Client làm biến toàn cầu an toàn khỏi điều kiện cuộc đua nếu có nhiều goroutines gọi một hàm sử dụng biến đó? –

+1

@ bn00d là 'phản hồi trì hoãn.Body.Close()' đúng? tôi hỏi vì bằng cách trì hoãn việc đóng, chúng tôi sẽ không thực sự đóng conn để tái sử dụng cho đến khi thoát khỏi chức năng chính, do đó người ta chỉ cần gọi '.Close()' trực tiếp sau '.ReadAll()'. điều này có thể không giống như một vấn đề trong ví dụ của bạn b/c nó không thực sự chứng minh làm cho nhiều req, nó chỉ đơn giản là làm cho một req và sau đó thoát nhưng nếu chúng tôi đã thực hiện một số req trở lại trở lại, nó sẽ có vẻ rằng từ 'trì hoãn 'ed,' .Close() 'sẽ không được gọi là til func exits. hoặc ... tôi đang thiếu một cái gì đó? cảm ơn. –

Các vấn đề liên quan