中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

golang 高并發(fā)下 tcp 建連數(shù)暴漲的原因分析

2018-07-20    來(lái)源:編程學(xué)習(xí)網(wǎng)

容器云強(qiáng)勢(shì)上線!快速搭建集群,上萬(wàn)Linux鏡像隨意使用

 背景:服務(wù)需要高頻發(fā)出GET請(qǐng)求,然后我們封裝的是 golang 的net/http 庫(kù), 因?yàn)殚_(kāi)源的比如req 和gorequsts 都是封裝的net/http ,所以我們還是選用原生(req 使用不當(dāng)也會(huì)掉坑里)。我們的場(chǎng)景是多協(xié)程從chan 中取任務(wù),并發(fā)get 請(qǐng)求,然后設(shè)置超時(shí),設(shè)置代理,完了。我們知道net/http 是自帶了連接池的,能自動(dòng)回收連接,但是,發(fā)現(xiàn)連接暴漲,起了1萬(wàn)個(gè)連接。

    首先,我們第一版的代碼是基于python 的,是沒(méi)有連接暴漲的問(wèn)題的,封裝的requests,封裝如下:

def fetch(self, url, body, method, proxies=None, header=None):
        
        res = None
        timeout = 4
        self.error = ''
        stream_flag = False

        if not header:
            header = {}

        if not proxies:
            proxies = {}

        try:
            self.set_extra(header)
            res = self.session.request(method, url, data=body, headers=header, timeout=timeout, proxies=proxies)

        # to do: self.error variable to logger
        except requests.exceptions.Timeout:
            self.error = "fetch faild !!! url:{0} except: connect timeout".format(url)
        except requests.exceptions.TooManyRedirects:
            self.error = "fetch faild !!! url:{0} except: redirect more than 3 times".format(url)
        except requests.exceptions.ConnectionError:
            self.error = "fetch faild !!! url:{0} except: connect error".format(url)
        except socket.timeout:
            self.error = "fetch faild !!! url:{0} except: recv timetout".format(url)
        except:
            self.error = "fetch faild !!! url:{0} except: {1}".format(url, traceback.format_exc())

        if res is not None and self.error == "":
            self.logger.info("url: %s, body: %s, method: %s, header: %s, proxy: %s, request success!", url, str(body)[:100], method, header, proxies)
            self.logger.info("url: %s, resp_header: %s, sock_ip: %s, response success!", url, res.headers, self.get_sock_ip(res))
        else:
            self.logger.warning("url: %s, body: %s, method: %s, header: %s, proxy: %s, error: %s, reuqest failed!", url, str(body)[:100], method, header, proxies, self.error)

        return res

    改用golang后,我們選擇的是net/http?磏et/http 的文檔,最基本的請(qǐng)求,如get,post 可以使用如下的方式:

resp, err := http.Get("http://example.com/")

resp, err := http.Post("http://example.com/upload", "image/jpeg", &buf)

resp, err := http.PostForm("http://example.com/form",url.Values{"key": {"Value"}, "id": {"123"}})

    我們需要添加超時(shí),代理和設(shè)置head 頭,官方推薦的是使用client 方式,如下:

client := &http.Client{

     CheckRedirect: redirectPolicyFunc,
     Timeout: time.Duration(10)*time.Second,//設(shè)置超時(shí)

}

client.Transport = &http.Transport{Proxy: http.ProxyURL(proxyUrl)} //設(shè)置代理ip

resp, err := client.Get("http://example.com")

req, err := http.NewRequest("GET", "http://example.com", nil) //設(shè)置header 

req.Header.Add("If-None-Match", `W/"wyzzy"`)

resp, err := client.Do(req)

    這里官方文檔指出,client 只需要全局實(shí)例化,然后是協(xié)程安全的,所以,使用多協(xié)程的方式,用共享的client 去發(fā)送req 是可行的。    

    根據(jù)官方文檔,和我們的業(yè)務(wù)場(chǎng)景,我們寫(xiě)出了如下的業(yè)務(wù)代碼:

var client *http.Client

//初始化全局client
func init (){
	client = &http.Client{
		Timeout: time.Duration(10)*time.Second,
	  }
}

type HttpClient struct {}

//提供給多協(xié)程調(diào)用
func (this *HttpClient) Fetch(dstUrl string, method string, proxyHost string, header map[string]string)(*http.Response){
    //實(shí)例化req
	req, _ := http.NewRequest(method, dstUrl, nil)
    //添加header
	for k, v := range header {
		req.Header.Add(k, v)
	}
    //添加代理ip
	proxy := "http://" + proxyHost
	proxyUrl, _ := url.Parse(proxy)
	client.Transport = &http.Transport{Proxy: http.ProxyURL(proxyUrl)}
	resp, err := client.Do(req)
	return resp, err
}

    當(dāng)我們使用協(xié)程池并發(fā)開(kāi)100個(gè) worker 調(diào)用Fetch() 的時(shí)候,照理說(shuō),established 的連接應(yīng)該是100個(gè),但是,我壓測(cè)的時(shí)候,發(fā)現(xiàn),established 的連接塊到一萬(wàn)個(gè)了,net/http的連接池根本沒(méi)起作用?估計(jì)這是哪里用法不對(duì)吧。

    使用python的庫(kù)并發(fā)請(qǐng)求是沒(méi)有任何問(wèn)題的,那這個(gè)問(wèn)題到底出在哪里?其實(shí)如果熟悉golang net/http庫(kù)的流程,就很清楚了,問(wèn)題就處在上面的Transport ,每個(gè)transport 維護(hù)了一個(gè)連接池,我們代碼中每個(gè)協(xié)程都會(huì)new 一個(gè)transport ,這樣,就會(huì)不斷新建連接。

    我們看下transport 的數(shù)據(jù)結(jié)構(gòu):

type Transport struct {
    idleMu     sync.Mutex
    wantIdle   bool // user has requested to close all idle conns
    idleConn   map[connectMethodKey][]*persistConn 
    idleConnCh map[connectMethodKey]chan *persistConn

    reqMu       sync.Mutex
    reqCanceler map[*Request]func()

    altMu    sync.RWMutex
    altProto map[string]RoundTripper // nil or map of URI scheme => RoundTripper
    //Dial獲取一個(gè)tcp 連接,也就是net.Conn結(jié)構(gòu),
    Dial func(network, addr string) (net.Conn, error)
}

         結(jié)構(gòu)體中兩個(gè)map, 保存的就是不同的協(xié)議 不同的host,到不同的請(qǐng)求 的映射。非常明顯,這個(gè)結(jié)構(gòu)體應(yīng)該是和client 一樣全局的。所以,為了避開(kāi)使用連接池失效,是不能不斷new transport 的!

        我們不斷new transport 的原因就是為了設(shè)置代理,這里不能使用這種方式了,那怎么達(dá)到目的?如果知道代理的原理,我們這里解決其實(shí)很簡(jiǎn)單,請(qǐng)求使用ip ,host 帶上域名就ok了。代碼如下:

var client *http.Client

func init (){
	client = &http.Client{}
}

type HttpClient struct {}

func NewHttpClient()(*HttpClient){
	httpClient := HttpClient{}
	return &httpClient
}

func (this *HttpClient) replaceUrl(srcUrl string, ip string)(string){
	httpPrefix := "http://"
	parsedUrl, err := url.Parse(srcUrl)
	if err != nil {
		return ""
	}
	return httpPrefix + ip + parsedUrl.Path
}

func (this *HttpClient) downLoadFile(resp *http.Response)(error){
	//err write /dev/null: bad file descriptor#
	out, err := os.OpenFile("/dev/null", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	defer out.Close()
	_, err = io.Copy(out, resp.Body)
	return err
}

func (this *HttpClient) Fetch(dstUrl string, method string, proxyHost string, header map[string]string, preload bool, timeout int64)(*http.Response, error){
	// proxyHost 換掉 url 中請(qǐng)求
	newUrl := this.replaceUrl(dstUrl, proxyHost)
	req, _ := http.NewRequest(method, newUrl, nil)
	for k, v := range header {
		req.Header.Add(k, v)
	}
	client.Timeout = time.Duration(timeout)*time.Second

	resp, err := client.Do(req)
    return resp, err
}

    使用header 中加host 的方式后,這里的tcp 建連數(shù) 立刻下降到和協(xié)程池?cái)?shù)量一致,問(wèn)題得到解決。

 

來(lái)自:https://studygolang.com/articles/12590#reply3

標(biāo)簽: 安全 代碼 域名

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請(qǐng)聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點(diǎn)!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請(qǐng)與原作者聯(lián)系。

上一篇:常用的幾種大數(shù)據(jù)架構(gòu)剖析

下一篇:微服務(wù)中 Dubbo 和 Spring Cloud 架構(gòu)技術(shù)路線對(duì)