欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺談golang fasthttp踩坑經(jīng)驗(yàn)

 更新時(shí)間:2021年11月03日 15:22:57   作者:ndsun  
本文主要介紹了golang fasthttp踩坑經(jīng)驗(yàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

一個(gè)簡單的系統(tǒng),結(jié)構(gòu)如下:

我們的服務(wù)A接受外部的http請求,然后通過golang的fasthttp將請求轉(zhuǎn)發(fā)給服務(wù)B,流程非常簡單。線上運(yùn)行一段時(shí)間之后,發(fā)現(xiàn)服務(wù)B完全不再接收任何請求,查看服務(wù)A的日志,發(fā)現(xiàn)大量的如下錯(cuò)誤

  從錯(cuò)誤原因看是因?yàn)檫B接被占滿導(dǎo)致的。進(jìn)入服務(wù)A的容器中(服務(wù)A和服務(wù)B都是通過docker啟動(dòng)的),通過netstat -anlp查看,發(fā)現(xiàn)有大量的tpc連接,處于ESTABLISH。我們采用的是長連接的方式,此時(shí)心里非常疑惑:1. fasthttp是能夠復(fù)用連接的,為什么還會有如此多的TCP連接,2.為什么這些連接不能夠使用了,出現(xiàn)上述異常,原因是什么?

  從fasthttpclient源碼出發(fā),我們調(diào)用請求轉(zhuǎn)發(fā)的時(shí)候是用的是

f.Client.DoTimeout(req, resp, f.ExecTimeout),其中f.Client是一個(gè)fasthttp.HostClient,f.ExecTimeout設(shè)置的是5s。
追查代碼,直到client.go中的這個(gè)方法

func (c *HostClient) doNonNilReqResp(req *Request, resp *Response) (bool, error) {
    if req == nil {
        panic("BUG: req cannot be nil")
    }
    if resp == nil {
        panic("BUG: resp cannot be nil")
    }
 
    atomic.StoreUint32(&c.lastUseTime, uint32(time.Now().Unix()-startTimeUnix))
 
    // Free up resources occupied by response before sending the request,
    // so the GC may reclaim these resources (e.g. response body).
    resp.Reset()
 
    // If we detected a redirect to another schema
    if req.schemaUpdate {
        c.IsTLS = bytes.Equal(req.URI().Scheme(), strHTTPS)
        c.Addr = addMissingPort(string(req.Host()), c.IsTLS)
        c.addrIdx = 0
        c.addrs = nil
        req.schemaUpdate = false
        req.SetConnectionClose()
    }
 
    cc, err := c.acquireConn()
    if err != nil {
        return false, err
    }
    conn := cc.c
 
    resp.parseNetConn(conn)
 
    if c.WriteTimeout > 0 {
        // Set Deadline every time, since golang has fixed the performance issue
        // See https://github.com/golang/go/issues/15133#issuecomment-271571395 for details
        currentTime := time.Now()
        if err = conn.SetWriteDeadline(currentTime.Add(c.WriteTimeout)); err != nil {
            c.closeConn(cc)
            return true, err
        }
    }
 
    resetConnection := false
    if c.MaxConnDuration > 0 && time.Since(cc.createdTime) > c.MaxConnDuration && !req.ConnectionClose() {
        req.SetConnectionClose()
        resetConnection = true
    }
 
    userAgentOld := req.Header.UserAgent()
    if len(userAgentOld) == 0 {
        req.Header.userAgent = c.getClientName()
    }
    bw := c.acquireWriter(conn)
    err = req.Write(bw)
 
    if resetConnection {
        req.Header.ResetConnectionClose()
    }
 
    if err == nil {
        err = bw.Flush()
    }
    if err != nil {
        c.releaseWriter(bw)
        c.closeConn(cc)
        return true, err
    }
    c.releaseWriter(bw)
 
    if c.ReadTimeout > 0 {
        // Set Deadline every time, since golang has fixed the performance issue
        // See https://github.com/golang/go/issues/15133#issuecomment-271571395 for details
        currentTime := time.Now()
        if err = conn.SetReadDeadline(currentTime.Add(c.ReadTimeout)); err != nil {
            c.closeConn(cc)
            return true, err
        }
    }
 
    if !req.Header.IsGet() && req.Header.IsHead() {
        resp.SkipBody = true
    }
    if c.DisableHeaderNamesNormalizing {
        resp.Header.DisableNormalizing()
    }
 
    br := c.acquireReader(conn)
    if err = resp.ReadLimitBody(br, c.MaxResponseBodySize); err != nil {
        c.releaseReader(br)
        c.closeConn(cc)
        // Don't retry in case of ErrBodyTooLarge since we will just get the same again.
        retry := err != ErrBodyTooLarge
        return retry, err
    }
    c.releaseReader(br)
 
    if resetConnection || req.ConnectionClose() || resp.ConnectionClose() {
        c.closeConn(cc)
    } else {
        c.releaseConn(cc)
    }
 
    return false, err
}

  請注意c.acquireConn()這個(gè)方法,這個(gè)方法即從連接池中獲取連接,如果沒有可用連接,則創(chuàng)建新的連接,該方法實(shí)現(xiàn)如下

func (c *HostClient) acquireConn() (*clientConn, error) {
    var cc *clientConn
    createConn := false
    startCleaner := false
 
    var n int
    c.connsLock.Lock()
    n = len(c.conns)
    if n == 0 {
        maxConns := c.MaxConns
        if maxConns <= 0 {
            maxConns = DefaultMaxConnsPerHost
        }
        if c.connsCount < maxConns {
            c.connsCount++
            createConn = true
            if !c.connsCleanerRun {
                startCleaner = true
                c.connsCleanerRun = true
            }
        }
    } else {
        n--
        cc = c.conns[n]
        c.conns[n] = nil
        c.conns = c.conns[:n]
    }
    c.connsLock.Unlock()
 
    if cc != nil {
        return cc, nil
    }
    if !createConn {
        return nil, ErrNoFreeConns
    }
 
    if startCleaner {
        go c.connsCleaner()
    }
 
    conn, err := c.dialHostHard()
    if err != nil {
        c.decConnsCount()
        return nil, err
    }
    cc = acquireClientConn(conn)
 
    return cc, nil
}

其中ErrNoFreeConns 即為errors.New("no free connections available to host"),該錯(cuò)誤就是我們服務(wù)中出現(xiàn)的錯(cuò)誤。那原因很明顯就是因?yàn)?!createConn,即無法創(chuàng)建新的連接,為什么無法創(chuàng)建新的連接,是因?yàn)檫B接數(shù)已經(jīng)達(dá)到了maxConns =DefaultMaxConnsPerHost = 512(默認(rèn)值)。連接數(shù)達(dá)到最大值了,但是為什么連接沒有回收也沒有復(fù)用,從這塊看,還是沒有看出來。又仔細(xì)的查了一下業(yè)務(wù)代碼,發(fā)現(xiàn)很多服務(wù)A到服務(wù)B的請求,都是因?yàn)槌瑫r(shí)了而結(jié)束的,即達(dá)到了f.ExecTimeout = 5s。

又從頭查看源碼,終于發(fā)現(xiàn)了玄機(jī)。

func clientDoDeadline(req *Request, resp *Response, deadline time.Time, c clientDoer) error {
    timeout := -time.Since(deadline)
    if timeout <= 0 {
        return ErrTimeout
    }
 
    var ch chan error
    chv := errorChPool.Get()
    if chv == nil {
        chv = make(chan error, 1)
    }
    ch = chv.(chan error)
 
    // Make req and resp copies, since on timeout they no longer
    // may be accessed.
    reqCopy := AcquireRequest()
    req.copyToSkipBody(reqCopy)
    swapRequestBody(req, reqCopy)
    respCopy := AcquireResponse()
    if resp != nil {
        // Not calling resp.copyToSkipBody(respCopy) here to avoid
        // unexpected messing with headers
        respCopy.SkipBody = resp.SkipBody
    }
 
    // Note that the request continues execution on ErrTimeout until
    // client-specific ReadTimeout exceeds. This helps limiting load
    // on slow hosts by MaxConns* concurrent requests.
    //
    // Without this 'hack' the load on slow host could exceed MaxConns*
    // concurrent requests, since timed out requests on client side
    // usually continue execution on the host.
 
    var mu sync.Mutex
    var timedout bool
        //這個(gè)goroutine是用來處理連接以及發(fā)送請求的
    go func() {
        errDo := c.Do(reqCopy, respCopy)
        mu.Lock()
        {
            if !timedout {
                if resp != nil {
                    respCopy.copyToSkipBody(resp)
                    swapResponseBody(resp, respCopy)
                }
                swapRequestBody(reqCopy, req)
                ch <- errDo
            }
        }
        mu.Unlock()
 
        ReleaseResponse(respCopy)
        ReleaseRequest(reqCopy)
    }()
        //這塊內(nèi)容是用來處理超時(shí)的
    tc := AcquireTimer(timeout)
    var err error
    select {
    case err = <-ch:
    case <-tc.C:
        mu.Lock()
        {
            timedout = true
            err = ErrTimeout
        }
        mu.Unlock()
    }
    ReleaseTimer(tc)
 
    select {
    case <-ch:
    default:
    }
    errorChPool.Put(chv)
 
    return err
}

  我們看到,請求的超時(shí)時(shí)間是如何處理的。當(dāng)我的請求超時(shí)后,主流程直接返回了超時(shí)錯(cuò)誤,而此時(shí),goroutine里面還在等待請求的返回,而偏偏B服務(wù),由于一些情況會拋出異常,也就是沒有對這個(gè)請求進(jìn)行返回,從而導(dǎo)致這個(gè)鏈接一直未得到釋放,終于解答了為什么有大量的連接一直被占有從而導(dǎo)致無連接可用的情況。

  最后,當(dāng)我心里還在腹誹為什么fasthttp這么優(yōu)秀的框架會有這種問題,如果服務(wù)端拋異常(不對請求進(jìn)行返回)就會把連接打滿?又自己看了一下代碼,原來,

// DoTimeout performs the given request and waits for response during
// the given timeout duration.
//
// Request must contain at least non-zero RequestURI with full url (including
// scheme and host) or non-zero Host header + RequestURI.
//
// The function doesn't follow redirects. Use Get* for following redirects.
//
// Response is ignored if resp is nil.
//
// ErrTimeout is returned if the response wasn't returned during
// the given timeout.
//
// ErrNoFreeConns is returned if all HostClient.MaxConns connections
// to the host are busy.
//
// It is recommended obtaining req and resp via AcquireRequest
// and AcquireResponse in performance-critical code.
//
// Warning: DoTimeout does not terminate the request itself. The request will
// continue in the background and the response will be discarded.
// If requests take too long and the connection pool gets filled up please
// try setting a ReadTimeout.
func (c *HostClient) DoTimeout(req *Request, resp *Response, timeout time.Duration) error {
    return clientDoTimeout(req, resp, timeout, c)
}

  人家這個(gè)方法的注釋早就說明了,看最后一段注釋,大意就是超時(shí)之后,請求依然會繼續(xù)等待返回值,只是返回值會被丟棄,如果請求時(shí)間太長,會把連接池占滿,正好是我們遇到的問題。為了解決,需要設(shè)置ReadTimeout字段,這個(gè)字段的我個(gè)人理解的意思就是當(dāng)請求發(fā)出之后,達(dá)到ReadTimeout時(shí)間還沒有得到返回值,客戶端就會把連接斷開(釋放)。

  以上就是這次經(jīng)驗(yàn)之談,切記,使用fasthttp的時(shí)候,加上ReadTimeout字段。

到此這篇關(guān)于淺談golang fasthttp踩坑經(jīng)驗(yàn)的文章就介紹到這了,更多相關(guān)golang fasthttp踩坑內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 關(guān)于Go語言中特有的設(shè)計(jì)模式與實(shí)現(xiàn)方式講解

    關(guān)于Go語言中特有的設(shè)計(jì)模式與實(shí)現(xiàn)方式講解

    雖然Go語言沒有像其他語言那樣明確的設(shè)計(jì)模式,但在實(shí)踐中,開發(fā)者們?nèi)匀话l(fā)現(xiàn)了一些在Go語言中特別適用的設(shè)計(jì)模式和實(shí)現(xiàn)方式,本文就來和大家一一進(jìn)行講解
    2023-05-05
  • Go語言map不支持并發(fā)寫操作的原因

    Go語言map不支持并發(fā)寫操作的原因

    Go語言為什么不支持并發(fā)讀寫map?,Go官方的說法是在多數(shù)情況下map只存在并發(fā)讀操作,如果原生支持并發(fā)讀寫,即降低了并發(fā)讀操作的性能,在使用?map?時(shí),要特別注意是否存在對?map?的并發(fā)寫操作,如果存在,要結(jié)合?sync?包的互斥鎖一起使用,
    2024-01-01
  • Go操作redis與redigo的示例解析

    Go操作redis與redigo的示例解析

    這篇文章主要為大家介紹了Go操作redis與redigo的示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪
    2022-04-04
  • Go語言數(shù)據(jù)結(jié)構(gòu)之單鏈表的實(shí)例詳解

    Go語言數(shù)據(jù)結(jié)構(gòu)之單鏈表的實(shí)例詳解

    鏈表由一系列結(jié)點(diǎn)(鏈表中每一個(gè)元素稱為結(jié)點(diǎn))組成,結(jié)點(diǎn)可以在運(yùn)行時(shí)動(dòng)態(tài)生成。本文將通過五個(gè)例題帶大家深入了解Go語言中單鏈表的用法,感興趣的可以了解一下
    2022-08-08
  • 詳解玩轉(zhuǎn)直播系列之消息模塊演進(jìn)

    詳解玩轉(zhuǎn)直播系列之消息模塊演進(jìn)

    本篇文章針對秀場直播,簡單地描述一下消息模型,說明一下我們消息模型的架構(gòu),并結(jié)合我們一年以來,通過處理不同的業(yè)務(wù)線上問題,來進(jìn)行演進(jìn)式的消息模型架構(gòu)的升級與調(diào)整,將此整理成文,并分享給大家
    2021-06-06
  • Go語言擴(kuò)展原語之ErrGroup的用法詳解

    Go語言擴(kuò)展原語之ErrGroup的用法詳解

    除標(biāo)準(zhǔn)庫中提供的同步原語外,Go語言還在子倉庫sync中提供了4種擴(kuò)展原語,本文主要為大家介紹的是其中的golang/sync/errgroup.Group,感興趣的小伙伴可以了解一下
    2023-07-07
  • go RWMutex的實(shí)現(xiàn)示例

    go RWMutex的實(shí)現(xiàn)示例

    本文主要來介紹讀寫鎖的一種Go語言的實(shí)現(xiàn)方式RWMutex,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • go語言處理TCP拆包/粘包的具體實(shí)現(xiàn)

    go語言處理TCP拆包/粘包的具體實(shí)現(xiàn)

    TCP的拆包/粘包也算是網(wǎng)絡(luò)編程中一個(gè)比較基礎(chǔ)的問題了,本文主要介紹了go語言處理TCP拆包/粘包,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-12-12
  • Golang熔斷器的開發(fā)過程詳解

    Golang熔斷器的開發(fā)過程詳解

    Golang熔斷器是一種用于處理分布式系統(tǒng)中服務(wù)調(diào)用的故障保護(hù)機(jī)制,它可以防止故障服務(wù)的連鎖反應(yīng),提高系統(tǒng)的穩(wěn)定性和可靠性,本文將給大家詳細(xì)的介紹一下Golang熔斷器的開發(fā)過程,需要的朋友可以參考下
    2023-09-09
  • Golang實(shí)現(xiàn)短網(wǎng)址/短鏈服務(wù)的開發(fā)筆記分享

    Golang實(shí)現(xiàn)短網(wǎng)址/短鏈服務(wù)的開發(fā)筆記分享

    這篇文章主要為大家詳細(xì)介紹了如何使用Golang實(shí)現(xiàn)短網(wǎng)址/短鏈服務(wù),文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以了解一下
    2023-05-05

最新評論