Obecnie staram się znaleźć sposób na ponowne wykorzystanie połączeń podczas tworzenia postów HTTP w Golang.
Stworzyłem transport i klienta takiego:
// Create a new transport and HTTP client
tr := &http.Transport{}
client := &http.Client{Transport: tr}
Następnie przekazuję ten wskaźnik klienta do goroutine, który tworzy wiele postów do tego samego punktu końcowego, tak jak poniżej:
r, err := client.Post(url, "application/json", post)
Patrząc na netstat, wydaje się, że powoduje to nowe połączenie dla każdego posta, co powoduje otwarcie dużej liczby równoczesnych połączeń.
Jaki jest właściwy sposób ponownego wykorzystania połączeń w tym przypadku?
Odpowiedzi:
Upewnij się, że czytasz, aż odpowiedź będzie kompletna ORAZ zadzwoń
Close()
.na przykład
Ponownie ... Aby zapewnić
http.Client
ponowne użycie połączenia, pamiętaj:ioutil.ReadAll(resp.Body)
)Body.Close()
źródło
defer res.Body.Close()
podobny program, ale od czasu do czasu wracałem z funkcji, zanim ta część została wykonana (resp.StatusCode != 200
na przykład jeśli ), co pozostawiło wiele otwartych deskryptorów plików bezczynnych i ostatecznie zabiło mój program. Kliknięcie tego wątku sprawiło, że ponownie wróciłem do tej części kodu i sam facepalm. dzięki.ioutil.ReadAll()
na pewno wystarczy, czy nadal muszę rozsiewaćio.Copy()
telefony w każdym miejscu, na wszelki wypadek?Jeśli ktoś nadal znajduje odpowiedzi, jak to zrobić, to właśnie ja to robię.
package main import ( "bytes" "io/ioutil" "log" "net/http" "time" ) var httpClient *http.Client const ( MaxIdleConnections int = 20 RequestTimeout int = 5 ) 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() { endPoint := "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") response, err := httpClient.Do(req) if err != nil && response == nil { log.Fatalf("Error sending request to API endpoint. %+v", err) } // 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
Podsumowując, tworzę inną metodę tworzenia klienta HTTP i przypisywania go do zmiennej globalnej, a następnie używam jej do wykonywania żądań. Zanotuj
defer response.Body.Close()
Spowoduje to zamknięcie połączenia i ponowne ustawienie go w stan gotowości do ponownego użycia.
Mam nadzieję, że to komuś pomoże.
źródło
defer response.Body.Close()
poprawne? pytam, ponieważ odraczając zamknięcie, nie zamkniemy połączenia do ponownego użycia, dopóki główna funkcja nie zostanie zakończona, dlatego należy po prostu wywołać.Close()
bezpośrednio po.ReadAll()
. to może nie wydawać się problemem w twoim przykładzie b / c tak naprawdę nie demonstruje tworzenia wielu żądań, po prostu tworzy jedno żądanie, a następnie wychodzi, ale gdybyśmy mieli wykonać kilka żądań jeden po drugim, wydawałoby się, że skorodefer
ed,.Close()
nie będą nazywane do momentu wyjścia funkcji. czy ... czy coś mi brakuje? dzięki.Edycja: jest to bardziej uwaga dla osób, które konstruują transport i klienta dla każdego żądania.
Edit2: Zmieniono link do godoc.
Transport
jest strukturą przechowującą połączenia do ponownego wykorzystania; zobacz https://godoc.org/net/http#Transport („Domyślnie Transport buforuje połączenia do ponownego wykorzystania w przyszłości”).Jeśli więc utworzysz nowy transport dla każdego żądania, za każdym razem utworzy on nowe połączenia. W tym przypadku rozwiązaniem jest udostępnienie jednej instancji Transport między klientami.
źródło
IIRC, domyślny klient robi ponownego wykorzystania połączeń. Zamykasz odpowiedź ?
źródło
*_WAIT
stanów lub coś w tym rodzajuo ciele
// It is the caller's responsibility to // close Body. The default HTTP client's Transport may not // reuse HTTP/1.x "keep-alive" TCP connections if the Body is // not read to completion and closed.
Więc jeśli chcesz ponownie używać połączeń TCP, musisz za każdym razem zamykać Body po zakończeniu odczytu. W ten sposób sugeruje się funkcję ReadBody (io.ReadCloser).
package main import ( "fmt" "io" "io/ioutil" "net/http" "time" ) func main() { req, err := http.NewRequest(http.MethodGet, "https://github.com", nil) if err != nil { fmt.Println(err.Error()) return } client := &http.Client{} i := 0 for { resp, err := client.Do(req) if err != nil { fmt.Println(err.Error()) return } _, _ = readBody(resp.Body) fmt.Println("done ", i) time.Sleep(5 * time.Second) } } func readBody(readCloser io.ReadCloser) ([]byte, error) { defer readCloser.Close() body, err := ioutil.ReadAll(readCloser) if err != nil { return nil, err } return body, nil }
źródło
Innym podejściem
init()
jest użycie metody singleton w celu pobrania klienta http. Używając sync.Once możesz mieć pewność, że tylko jedno wystąpienie będzie używane we wszystkich Twoich żądaniach.var ( once sync.Once netClient *http.Client ) func newNetClient() *http.Client { once.Do(func() { var netTransport = &http.Transport{ Dial: (&net.Dialer{ Timeout: 2 * time.Second, }).Dial, TLSHandshakeTimeout: 2 * time.Second, } netClient = &http.Client{ Timeout: time.Second * 2, Transport: netTransport, } }) return netClient } func yourFunc(){ URL := "local.dev" req, err := http.NewRequest("POST", URL, nil) response, err := newNetClient().Do(req) // ... }
źródło
Brakującym punktem jest tutaj kwestia „gorutyny”. Transport ma własną pulę połączeń, domyślnie każde połączenie w tej puli jest ponownie wykorzystywane (jeśli treść jest w pełni odczytana i zamknięta), ale jeśli kilka goroutin wysyła żądania, zostaną utworzone nowe połączenia (pula ma wszystkie połączenia zajęte i utworzy nowe ). Aby rozwiązać ten problem, musisz ograniczyć maksymalną liczbę połączeń na hosta:
Transport.MaxConnsPerHost
( https://golang.org/src/net/http/transport.go#L205 ).Prawdopodobnie chcesz również skonfigurować
IdleConnTimeout
i / lubResponseHeaderTimeout
.źródło
https://golang.org/src/net/http/transport.go#L196
powinieneś ustawić
MaxConnsPerHost
jawnie na swojehttp.Client
.Transport
ponownie wykorzystuje połączenie TCP, ale należy ograniczyćMaxConnsPerHost
(domyślnie 0 oznacza brak ograniczeń).func init() { // singleton http.Client httpClient = createHTTPClient() } // createHTTPClient for connection re-use func createHTTPClient() *http.Client { client := &http.Client{ Transport: &http.Transport{ MaxConnsPerHost: 1, // other option field }, Timeout: time.Duration(RequestTimeout) * time.Second, } return client }
źródło
Istnieją dwa możliwe sposoby:
Użyj biblioteki, która wewnętrznie ponownie wykorzystuje i zarządza deskryptorami plików, powiązanymi z każdym żądaniem. Klient HTTP robi to samo wewnętrznie, ale wtedy masz kontrolę nad tym, ile równoczesnych połączeń chcesz otworzyć i jak zarządzać swoimi zasobami. Jeśli jesteś zainteresowany, spójrz na implementację netpoll, która wewnętrznie używa epoll / kqueue do zarządzania nimi.
Najprościej byłoby, zamiast gromadzić połączenia sieciowe, utworzyć pulę pracowników dla swoich gorutyn. Byłoby to łatwe i lepsze rozwiązanie, które nie utrudniałoby pracy z obecną bazą kodu i wymagałoby niewielkich zmian.
Załóżmy, że po otrzymaniu żądania musisz wysłać n żądania POST.
Możesz użyć kanałów, aby to zaimplementować.
Lub po prostu możesz skorzystać z bibliotek innych firm.
Na przykład: https://github.com/ivpusic/grpool
źródło