Повторное использование http-соединений в Голанге
В настоящее время я пытаюсь найти способ повторного использования соединений при создании сообщений HTTP в Голанге.
Я создал транспорт и клиент так:
// Create a new transport and HTTP client
tr := &http.Transport{}
client := &http.Client{Transport: tr}
Затем я передаю этот клиентский указатель в goroutine, который делает несколько сообщений в одной и той же конечной точке следующим образом:
r, err := client.Post(url, "application/json", post)
Если посмотреть на netstat, это, как представляется, приведет к новому соединению для каждого сообщения, в результате чего будет открыто большое количество одновременных подключений.
Каков правильный способ повторного использования соединений в этом случае?
Ответы
Ответ 1
Вы должны убедиться, что вы прочитали, пока ответ не будет завершен, прежде чем вызывать Close()
.
например.
res, _ := client.Do(req)
io.Copy(ioutil.Discard, res.Body)
res.Body.Close()
Для обеспечения повторного использования повторного использования http.Client
обязательно выполните две вещи:
- Чтение до завершения ответа (т.е.
ioutil.ReadAll(resp.Body)
)
- Вызов
Body.Close()
Ответ 2
Изменение: Это больше примечание для людей, которые создают Транспорт и Клиент для каждого запроса.
Edit2: изменена ссылка на godoc.
Transport
- это структура, которая содержит соединения для повторного использования; см. https://godoc.org/net/http#Transport ("По умолчанию Transport кэширует соединения для последующего повторного использования.")
Поэтому, если вы создаете новый транспорт для каждого запроса, он будет каждый раз создавать новые подключения. В этом случае решение состоит в том, чтобы разделить один экземпляр Transport между клиентами.
Ответ 3
Если кто-то все еще находит ответы на вопрос о том, как это сделать, вот как я это делаю.
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 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 Play: http://play.golang.org/p/oliqHLmzSX
В общем, я создаю другой метод для создания HTTP-клиента и назначения его глобальной переменной, а затем используя его для выполнения запросов. Обратите внимание
defer response.Body.Close()
Это закроет соединение и снова настроит его на повторное использование.
Надеюсь, это поможет кому-то.
Ответ 4
IIRC, клиент по умолчанию повторно использует соединения. Вы закрываете ответ?
Звонящие должны закрыть resp.Body, когда закончите читать с него. Если resp.Body не закрыт, Клиент, лежащий в основе RoundTripper (обычно Transport), возможно, не сможет повторно использовать постоянное TCP-соединение с сервером для последующего запроса "keep-alive".
Ответ 5
о теле
// It is the caller responsibility to
// close Body. The default HTTP client Transport may not
// reuse HTTP/1.x "keep-alive" TCP connections if the Body is
// not read to completion and closed.
Поэтому, если вы хотите повторно использовать TCP-соединения, вам нужно закрыть Тело каждый раз после чтения до завершения. Для этого предлагается функция 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
}
Ответ 6
Другой подход к init() - использовать одноэлементный метод для получения http-клиента. Используя sync.Once, вы можете быть уверены, что только один экземпляр будет использоваться для всех ваших запросов.
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)
// ...
}
Ответ 7
Существует два возможных способа:
-
Используйте библиотеку, которая внутренне повторно использует и управляет файловыми дескрипторами, связанными с каждым запросом. Http Client делает то же самое внутри, но тогда у вас будет контроль над тем, сколько одновременных подключений открывается и как управлять вашими ресурсами. Если вам интересно, посмотрите на реализацию netpoll, которая внутренне использует epoll/kqueue для управления ими.
-
Легким было бы, вместо объединения сетевых подключений, создать рабочий пул для ваших goroutines. Это было бы легким и лучшим решением, которое не помешало бы вашей текущей кодовой базе и потребовало бы незначительных изменений.
Предположим, вам нужно сделать запрос n POST после получения запроса.
![enter image description here]()
![enter image description here]()
Вы можете использовать каналы, чтобы реализовать это.
Или просто вы можете использовать сторонние библиотеки.
Например: https://github.com/ivpusic/grpool