Ich habe derzeit Probleme, einen Weg zu finden, um Verbindungen beim Erstellen von HTTP-Posts in Golang wiederzuverwenden.
Ich habe einen Transport und einen Client wie folgt erstellt:
// Create a new transport and HTTP client
tr := &http.Transport{}
client := &http.Client{Transport: tr}
Ich übergebe diesen Client-Zeiger dann an eine Goroutine, die mehrere Posts an denselben Endpunkt wie folgt erstellt:
r, err := client.Post(url, "application/json", post)
Wenn man sich netstat ansieht, scheint dies zu einer neuen Verbindung für jeden Beitrag zu führen, was dazu führt, dass eine große Anzahl gleichzeitiger Verbindungen offen ist.
Was ist in diesem Fall der richtige Weg, um Verbindungen wiederzuverwenden?
Antworten:
Stellen Sie sicher, dass Sie lesen, bis die Antwort vollständig ist UND anrufen
Close()
.z.B
Nochmals ... Um die
http.Client
Wiederverwendung der Verbindung sicherzustellen , gehen Sie wie folgt vor:ioutil.ReadAll(resp.Body)
)Body.Close()
quelle
defer res.Body.Close()
ein ähnliches Programm aufgerufen habe , aber gelegentlich von der Funktion zurückgekehrt bin , bevor dieser Teil ausgeführt wurde (wennresp.StatusCode != 200
zum Beispiel), wodurch viele offene Dateideskriptoren im Leerlauf blieben und schließlich mein Programm beendet haben. Wenn ich diesen Thread getroffen habe, habe ich diesen Teil des Codes und des Facepalms selbst noch einmal besucht. Vielen Dank.ioutil.ReadAll()
garantiert genug oder muss ich immer nochio.Copy()
Anrufe überall verteilen, nur für den Fall?Wenn noch jemand Antworten dazu findet, mache ich das so.
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
Zusammenfassend erstelle ich eine andere Methode, um einen HTTP-Client zu erstellen, ihn einer globalen Variablen zuzuweisen und ihn dann zum Erstellen von Anforderungen zu verwenden. Beachten Sie das
defer response.Body.Close()
Dadurch wird die Verbindung geschlossen und wieder für die Wiederverwendung vorbereitet.
Hoffe das wird jemandem helfen.
quelle
defer response.Body.Close()
richtig? Ich frage, weil wir durch Verschieben des Schließens den Conn erst wieder schließen, wenn die Hauptfunktion beendet ist. Daher sollte man einfach.Close()
direkt danach aufrufen.ReadAll()
. Dies scheint in Ihrem Beispiel kein Problem zu sein. B / C zeigt nicht, dass mehrere Anforderungen gestellt werden. Es wird lediglich eine Anforderung erstellt und dann beendet. Wenn wir jedoch mehrere Anforderungen hintereinander stellen, scheint es, dass seitdefer
ed..Close()
wird nicht genannt, bis func beendet wird. oder ... vermisse ich etwas? Vielen Dank.Bearbeiten: Dies ist eher ein Hinweis für Personen, die für jede Anforderung einen Transport und einen Client erstellen.
Edit2: Link zu Godoc geändert.
Transport
ist die Struktur, die Verbindungen zur Wiederverwendung enthält; Siehe https://godoc.org/net/http#Transport ("Standardmäßig transportiert Transport Verbindungen für die zukünftige Wiederverwendung zwischen.")Wenn Sie also für jede Anforderung einen neuen Transport erstellen, werden jedes Mal neue Verbindungen erstellt. In diesem Fall besteht die Lösung darin, die eine Transportinstanz zwischen Clients zu teilen.
quelle
IIRC, der Standardclient, verwendet Verbindungen wieder. Schließen Sie die Antwort ?
quelle
*_WAIT
Staaten oder so ähnlich seinüber Körper
// 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.
Wenn Sie also TCP-Verbindungen wiederverwenden möchten, müssen Sie Body jedes Mal nach dem vollständigen Lesen schließen. Eine Funktion ReadBody (io.ReadCloser) wird wie folgt vorgeschlagen.
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 }
quelle
Ein anderer Ansatz
init()
besteht darin, eine Singleton-Methode zu verwenden, um den http-Client abzurufen. Mit sync.Once können Sie sicher sein, dass für alle Ihre Anforderungen nur eine Instanz verwendet wird.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) // ... }
quelle
Der fehlende Punkt hier ist die "Goroutine" Sache. Transport hat einen eigenen Verbindungspool. Standardmäßig wird jede Verbindung in diesem Pool wiederverwendet (wenn der Text vollständig gelesen und geschlossen ist). Wenn jedoch mehrere Goroutinen Anforderungen senden, werden neue Verbindungen erstellt (der Pool hat alle Verbindungen besetzt und erstellt neue ). Um dies zu lösen, müssen Sie die maximale Anzahl von Verbindungen pro Host begrenzen:
Transport.MaxConnsPerHost
( https://golang.org/src/net/http/transport.go#L205 ).Wahrscheinlich möchten Sie auch einrichten
IdleConnTimeout
und / oderResponseHeaderTimeout
.quelle
https://golang.org/src/net/http/transport.go#L196
Sie sollten
MaxConnsPerHost
explizit auf Ihre setzenhttp.Client
.Transport
verwendet die TCP-Verbindung wieder, aber Sie sollten die einschränkenMaxConnsPerHost
(Standard 0 bedeutet keine Begrenzung).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 }
quelle
Es gibt zwei Möglichkeiten:
Verwenden Sie eine Bibliothek, die die mit den einzelnen Anforderungen verknüpften Dateideskriptoren intern wiederverwendet und verwaltet. Der HTTP-Client macht intern dasselbe, aber dann haben Sie die Kontrolle darüber, wie viele gleichzeitige Verbindungen geöffnet werden sollen und wie Sie Ihre Ressourcen verwalten. Wenn Sie interessiert sind, schauen Sie sich die Netpoll-Implementierung an, die intern epoll / kqueue verwendet, um sie zu verwalten.
Am einfachsten wäre es, anstatt Netzwerkverbindungen zu bündeln, einen Arbeiterpool für Ihre Goroutinen zu erstellen. Dies wäre eine einfache und bessere Lösung, die Ihre aktuelle Codebasis nicht behindert und geringfügige Änderungen erfordert.
Nehmen wir an, Sie müssen n POST-Anfragen stellen, nachdem Sie eine Anfrage erhalten haben.
Sie könnten Kanäle verwenden, um dies zu implementieren.
Oder Sie können einfach Bibliotheken von Drittanbietern verwenden.
Gefällt mir : https://github.com/ivpusic/grpool
quelle