Actualmente estoy luchando por encontrar una manera de reutilizar las conexiones cuando hago publicaciones HTTP en Golang.
He creado un transporte y un cliente así:
// Create a new transport and HTTP client
tr := &http.Transport{}
client := &http.Client{Transport: tr}
Luego paso este puntero de cliente a una goroutine que está haciendo múltiples publicaciones en el mismo punto final así:
r, err := client.Post(url, "application/json", post)
Al observar netstat, esto parece estar dando como resultado una nueva conexión para cada publicación, lo que resulta en una gran cantidad de conexiones simultáneas abiertas.
¿Cuál es la forma correcta de reutilizar las conexiones en este caso?
Respuestas:
Asegúrese de leer hasta que la respuesta esté completa Y llame
Close()
.p.ej
Nuevamente ... Para garantizar la
http.Client
reutilización de la conexión, asegúrese de:ioutil.ReadAll(resp.Body)
)Body.Close()
fuente
defer res.Body.Close()
a un programa similar, pero terminé volviendo de la función ocasionalmente antes de que se ejecutara esa parte (siresp.StatusCode != 200
, por ejemplo), lo que dejó muchos descriptores de archivos abiertos inactivos y finalmente mató mi programa. Golpear este hilo me hizo volver a visitar esa parte del código y hacerme facepalm. Gracias.ioutil.ReadAll()
garantiza que será suficiente o todavía necesito esparcirio.Copy()
llamadas por todos lados, por si acaso?Si alguien todavía encuentra respuestas sobre cómo hacerlo, así es como lo estoy haciendo.
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)) }
Ir al patio de juegos: http://play.golang.org/p/oliqHLmzSX
En resumen, estoy creando un método diferente para crear un cliente HTTP y asignarlo a una variable global y luego usarlo para realizar solicitudes. Nota la
defer response.Body.Close()
Esto cerrará la conexión y la preparará para su reutilización nuevamente.
Espero que esto ayude a alguien.
fuente
defer response.Body.Close()
correcto? pregunto porque al aplazar el cierre no cerraremos la conexión para su reutilización hasta que la función principal salga, por lo que simplemente se debe llamar.Close()
directamente después.ReadAll()
. esto puede no parecer un problema en su ejemplo porque en realidad no demuestra hacer múltiples req, simplemente hace un req y luego sale, pero si tuviéramos que hacer varios req seguidos, parecería que desdedefer
ed,.Close()
no se llamará hasta que la función salga. ¿O me estoy perdiendo algo? Gracias.Editar: Esto es más una nota para las personas que construyen un Transporte y un Cliente para cada solicitud.
Edit2: enlace cambiado a godoc.
Transport
es la estructura que contiene las conexiones para su reutilización; consulte https://godoc.org/net/http#Transport ("De forma predeterminada, el transporte almacena en caché las conexiones para reutilizarlas en el futuro").Entonces, si crea un nuevo transporte para cada solicitud, creará nuevas conexiones cada vez. En este caso, la solución es compartir la única instancia de transporte entre clientes.
fuente
IIRC, el cliente predeterminado hace conexiones de reutilización. ¿Estás cerrando la respuesta ?
fuente
*_WAIT
estados o algo asísobre el cuerpo
// 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.
Entonces, si desea reutilizar las conexiones TCP, debe cerrar Body cada vez que se completa la lectura. Se sugiere una función ReadBody (io.ReadCloser) así.
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 }
fuente
Otro enfoque
init()
es utilizar un método singleton para obtener el cliente http. Al usar sync.Once, puede estar seguro de que solo se usará una instancia en todas sus solicitudes.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) // ... }
fuente
El punto que falta aquí es la cosa de "goroutine". El transporte tiene su propio grupo de conexiones, de forma predeterminada, cada conexión en ese grupo se reutiliza (si el cuerpo se lee y cierra por completo), pero si varias rutinas están enviando solicitudes, se crearán nuevas conexiones (el grupo tiene todas las conexiones ocupadas y creará otras nuevas ). Para resolver eso, deberá limitar el número máximo de conexiones por host:
Transport.MaxConnsPerHost
( https://golang.org/src/net/http/transport.go#L205 ).Probablemente también desee configurar
IdleConnTimeout
y / oResponseHeaderTimeout
.fuente
https://golang.org/src/net/http/transport.go#L196
debe establecer
MaxConnsPerHost
explícitamente suhttp.Client
.Transport
reutiliza la conexión TCP, pero debe limitar laMaxConnsPerHost
(el valor predeterminado 0 significa que no hay límite).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 }
fuente
Hay dos formas posibles:
Utilice una biblioteca que reutilice y administre internamente los descriptores de archivos asociados con cada solicitud. Http Client hace lo mismo internamente, pero entonces usted tendría el control sobre cuántas conexiones simultáneas abrir y cómo administrar sus recursos. Si está interesado, mire la implementación de netpoll, que internamente usa epoll / kqueue para administrarlos.
La más fácil sería, en lugar de agrupar las conexiones de red, crear un grupo de trabajadores para sus rutinas gordas. Esta sería una solución fácil y mejor, que no obstaculizaría su base de código actual y requeriría cambios menores.
Supongamos que necesita realizar una solicitud POST, después de recibir una solicitud.
Podrías usar canales para implementar esto.
O simplemente puede usar bibliotecas de terceros.
Me gusta: https://github.com/ivpusic/grpool
fuente