गोलंग में http कनेक्शन का पुन: उपयोग


82

मैं वर्तमान में गोलांग में HTTP पोस्ट बनाते समय कनेक्शन का पुन: उपयोग करने का एक तरीका खोजने के लिए संघर्ष कर रहा हूं।

मैंने एक ट्रांसपोर्ट और क्लाइंट बनाया है जैसे:

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

मैं फिर इस क्लाइंट पॉइंटर को एक गोरोइन में दे रहा हूं जो एक ही समापन बिंदु पर कई पोस्ट बना रहा है जैसे:

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

नेटस्टैट को देखने से यह प्रतीत होता है कि प्रत्येक पद के लिए एक नया कनेक्शन है जिसके परिणामस्वरूप बड़ी संख्या में समवर्ती कनेक्शन खुले हैं।

इस मामले में कनेक्शन का पुन: उपयोग करने का सही तरीका क्या है?


2
इस प्रश्न का सही उत्तर इस डुप्लिकेट पर पोस्ट किया गया है: गो क्लाइंट प्रोग्राम TIME_WAIT राज्य में बहुत सारे सॉकेट बनाता है
ब्रेंट बर्नबर्न

जवाबों:


96

सुनिश्चित करें कि आप प्रतिक्रिया पूर्ण होने तक कॉल करें और कॉल करें Close()

जैसे

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

फिर से ... http.Clientकनेक्शन पुन: उपयोग सुनिश्चित करने के लिए सुनिश्चित करें:

  • जब तक रिस्पांस पूरा न हो जाए (यानी ioutil.ReadAll(resp.Body))
  • कॉल Body.Close()

1
मैं उसी मेजबान को पोस्ट कर रहा हूं। हालाँकि, मेरी समझ यह है कि MaxIdleConnsPerHost के परिणामस्वरूप निष्क्रिय कनेक्शन बंद हो जाएंगे। क्या यह मामला नहीं है?
सिक्र

5
+1, क्योंकि मैंने defer res.Body.Close()एक समान कार्यक्रम में कॉल किया था, लेकिन उस हिस्से को निष्पादित करने से पहले कभी-कभी फ़ंक्शन से वापस आना समाप्त हो जाता था (यदि resp.StatusCode != 200, उदाहरण के लिए), जिसने बहुत सारी खुली फ़ाइल विवरणकर्ताओं को निष्क्रिय कर दिया और अंततः मेरे कार्यक्रम को मार दिया। इस धागे को मारने से मुझे कोड का वह हिस्सा और खुद फेसपालम फिर से मिल गया। धन्यवाद।
sa125

3
एक दिलचस्प बात यह है कि पढ़ा गया कदम आवश्यक और पर्याप्त प्रतीत होता है। रीड-स्टेप अकेले पूल में कनेक्शन लौटाएगा, लेकिन करीब अकेले नहीं होगा; कनेक्शन TCP_WAIT में समाप्त होगा। इसके अलावा मुसीबत में भाग गया क्योंकि मैं प्रतिक्रिया पढ़ने के लिए एक json.NewDecoder () का उपयोग कर रहा था। बॉडी, जो इसे पूरी तरह से नहीं करता था। यदि आपको यकीन नहीं है तो io.Copy (ioutil.Discard, res.Body) को शामिल करना सुनिश्चित करें।
सैम रसेल

3
क्या यह जांचने का कोई तरीका है कि क्या शरीर को पूरी तरह से पढ़ा गया है? एक है ioutil.ReadAll()पर्याप्त होने की गारंटी या मैं अभी भी छिड़क करने की आवश्यकता है io.Copy(), हर जगह कॉल सिर्फ मामले में?
पेट्रिक इसलिंड

4
मैंने स्रोत कोड को देखा और ऐसा लगता है कि प्रतिक्रिया बॉडी क्लोज़ () पहले से ही शरीर को
ख़त्म करने

45

अगर किसी को अभी भी यह करने के लिए उत्तर मिल रहा है, तो यह है कि मैं यह कैसे कर रहा हूं।

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

सारांश में, मैं एक HTTP क्लाइंट बनाने और इसे वैश्विक चर को असाइन करने और फिर अनुरोध करने के लिए इसका उपयोग करने के लिए एक अलग विधि बना रहा हूं। ध्यान दें

defer response.Body.Close() 

यह कनेक्शन बंद कर देगा और इसे पुन: उपयोग के लिए तैयार कर देगा।

आशा है कि यह किसी की मदद करेगा।


1
Http.Client का उपयोग वैश्‍विक चर के रूप में दौड़ की स्थितियों से सुरक्षित है यदि उस चर का उपयोग करके एक फ़ंक्शन को कॉल करने वाले कई गोरोएंट हैं?
बार्ट सिल्वरस्ट्रिम

3
@ bn00d defer response.Body.Close()सही है? मैं क्योंकि करीब हम नहीं वास्तव में पास मुख्य कार्य बाहर निकलता है जब तक पुनः उपयोग के लिए कोन, इस प्रकार एक बस बुलाना चाहिए होगा defering से पूछना .Close()सीधे के बाद .ReadAll()। यह आपके उदाहरण b / c में एक समस्या की तरह प्रतीत नहीं हो सकता है, यह वास्तव में कई req बनाने का प्रदर्शन नहीं करता है, यह बस एक req बनाता है और फिर बाहर निकलता है, लेकिन अगर हम कई req वापस बनाने के लिए थे, तो ऐसा लगेगा कि defered के बाद से , .Close()टीआईएल फंक एग्जिट नहीं कहा जाएगा। या क्या मैं कुछ न कुछ भूल रहा हूं? धन्यवाद।
mad.meesh

1
@ mad.meesh यदि आप कई कॉल करते हैं (जैसे एक लूप के अंदर), बस कॉल को Body.Close () को एक क्लोजर के अंदर लपेटें, इस तरह से यह बंद हो जाएगा जैसे ही आप डेटा प्रोसेसिंग करेंगे।
एंटोनी ने

मैं इस तरह से हर अनुरोध के लिए अलग-अलग प्रॉक्सी कैसे सेट कर सकता हूं? क्या यह संभव है ?
अमीर खुशहाल

@ bn00d आपका उदाहरण काम नहीं कर रहा है। लूप जोड़ने के बाद, प्रत्येक अनुरोध अभी भी एक नए कनेक्शन में परिणाम करता है। play.golang.org/p/9Ah_lyfYxgV
लुईस चैन

37

संपादित करें: यह उन लोगों के लिए एक नोट है जो हर अनुरोध के लिए परिवहन और ग्राहक का निर्माण करते हैं।

एडिट 2: गोडॉक से परिवर्तित लिंक।

Transportवह संरचना है जो पुन: उपयोग के लिए कनेक्शन रखती है; देख https://godoc.org/net/http#Transport ( "डिफ़ॉल्ट रूप से, परिवहन भविष्य पुनः उपयोग के लिए कनेक्शन संचित करता है।")

इसलिए यदि आप प्रत्येक अनुरोध के लिए एक नया परिवहन बनाते हैं, तो यह हर बार नए कनेक्शन बनाएगा। इस मामले में समाधान ग्राहकों के बीच एक परिवहन उदाहरण साझा करना है।


कृपया विशिष्ट प्रतिबद्ध का उपयोग करके लिंक दें। आपका लिंक अब सही नहीं है
इनक गमुस

play.golang.org/p/9Ah_lyfYxgV यह उदाहरण सिर्फ एक परिवहन दिखाता है, लेकिन यह अभी भी प्रति अनुरोध एक कनेक्शन को स्पैन करता है। ऐसा क्यों है ?
लुईस चैन

12

IIRC, डिफ़ॉल्ट क्लाइंट कनेक्शन का पुन: उपयोग करता है। क्या आप प्रतिक्रिया बंद कर रहे हैं ?

कॉलर्स को सम्मान देना चाहिए। यदि resp.Body बंद नहीं है, तो क्लाइंट का अंतर्निहित राउंडटाइपर (आमतौर पर ट्रांसपोर्ट) बाद में "रखने के लिए जीवित" अनुरोध के लिए सर्वर पर लगातार टीसीपी कनेक्शन का फिर से उपयोग करने में सक्षम नहीं हो सकता है।


हाय, प्रतिक्रिया के लिए धन्यवाद। हां, माफ करना मुझे भी इसमें शामिल होना चाहिए था। मैं r.Body.Close () के साथ कनेक्शन बंद कर रहा हूं।
सिक्र

@sicr, क्या आप सकारात्मक हैं सर्वर वास्तव में कनेक्शन को बंद नहीं करता है? मेरा मतलब है, ये बकाया कनेक्शन *_WAITराज्यों में से एक में हो सकते हैं या कुछ इस तरह से हो सकते हैं
kostix

1
@kostix मुझे नेटस्टैट को देखते हुए बड़ी संख्या में कनेक्शन के साथ स्थापित किया गया है। ऐसा प्रतीत होता है कि प्रत्येक POST अनुरोध पर एक नए कनेक्शन को जन्म दिया जा रहा है क्योंकि उसी कनेक्शन का पुन: उपयोग किया जा रहा है।
सिक्र

@sicr, क्या आपको कनेक्शन पुनः उपयोग के बारे में कोई समाधान मिला? बहुत धन्यवाद, Daniele
Daniele B

3

शरीर के बारे में

// 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.

इसलिए यदि आप टीसीपी कनेक्शन का पुन: उपयोग करना चाहते हैं, तो आपको पूरा होने के बाद हर बार बॉडी को बंद करना होगा। एक फ़ंक्शन 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
}

2

करने के लिए एक और दृष्टिकोण init()http ग्राहक पाने के लिए एक सिंगलटन विधि का उपयोग करने के लिए है। सिंक का उपयोग करके। आप सुनिश्चित कर सकते हैं कि आपके सभी अनुरोधों पर केवल एक ही उदाहरण का उपयोग किया जाएगा।

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)
    // ...
}


यह पूरी तरह से मेरे लिए 100 HTTP अनुरोधों को संभालने के लिए पूरी तरह से काम करता है
मृदुल्यो

0

यहाँ गायब बिंदु "गोरोइन" है। ट्रांसपोर्ट का अपना कनेक्शन पूल है, डिफ़ॉल्ट रूप से उस पूल में प्रत्येक कनेक्शन का पुन: उपयोग किया जाता है (यदि शरीर को पूरी तरह से पढ़ा और बंद किया गया है) लेकिन यदि कई गोरआउट अनुरोध भेज रहे हैं, तो नए कनेक्शन बनाए जाएंगे (पूल में सभी कनेक्शन व्यस्त हैं और नए बनाए जाएंगे ) है। यह हल करने के लिए आपको प्रति होस्ट की अधिकतम संख्या को सीमित करने की आवश्यकता होगी: Transport.MaxConnsPerHost( https://golang.org/src/net/http/transport.go#L205 )।

शायद आप भी सेटअप IdleConnTimeoutऔर / या करना चाहते हैं ResponseHeaderTimeout


0

https://golang.org/src/net/http/transport.go#L196

आपको MaxConnsPerHostअपने को स्पष्ट रूप से सेट करना चाहिए http.ClientTransportTCP कनेक्शन का पुन: उपयोग करता है, लेकिन आपको MaxConnsPerHostडिफ़ॉल्ट (डिफ़ॉल्ट 0 का अर्थ है कोई सीमा नहीं) को सीमित करना चाहिए ।

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
}

-3

दो संभावित तरीके हैं:

  1. एक पुस्तकालय का उपयोग करें जो आंतरिक रूप से पुन: उपयोग करता है और प्रत्येक अनुरोध के साथ जुड़े फ़ाइल डिस्क्रिप्टर का प्रबंधन करता है। Http क्लाइंट आंतरिक रूप से एक ही काम करता है, लेकिन फिर आपके पास नियंत्रण होगा कि कितने समवर्ती कनेक्शन खोले जाएं, और अपने संसाधनों का प्रबंधन कैसे करें। यदि आप रुचि रखते हैं, तो नेटपोल कार्यान्वयन को देखें, जो उन्हें प्रबंधित करने के लिए आंतरिक रूप से एपोल / केक्यू का उपयोग करता है।

  2. नेटवर्क कनेक्शन को पूल करने के बजाय, आपके गोरआउट के लिए एक कार्यकर्ता पूल बनाना आसान होगा। यह आसान, और बेहतर समाधान होगा, जो आपके वर्तमान कोडबेस के साथ बाधा नहीं बनेगा, और इसमें मामूली बदलाव की आवश्यकता होगी।

मान लीजिए कि आपको एक अनुरोध प्राप्त करने के बाद, आपको n POST अनुरोध करने की आवश्यकता है।

यहाँ छवि विवरण दर्ज करें

यहाँ छवि विवरण दर्ज करें

इसे लागू करने के लिए आप चैनलों का उपयोग कर सकते हैं।

या, बस आप तीसरे पक्ष के पुस्तकालयों का उपयोग कर सकते हैं।
जैसे: https://github.com/ivpusic/grpool

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.