最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

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

我們的服務(wù)A接受外部的http請(qǐng)求,然后通過golang的fasthttp將請(qǐng)求轉(zhuǎn)發(fā)給服務(wù)B,流程非常簡(jiǎn)單。線上運(yùn)行一段時(shí)間之后,發(fā)現(xiàn)服務(wù)B完全不再接收任何請(qǐng)求,查看服務(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。我們采用的是長(zhǎng)連接的方式,此時(shí)心里非常疑惑:1. fasthttp是能夠復(fù)用連接的,為什么還會(huì)有如此多的TCP連接,2.為什么這些連接不能夠使用了,出現(xiàn)上述異常,原因是什么?

  從fasthttpclient源碼出發(fā),我們調(diào)用請(qǐng)求轉(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
}

  請(qǐng)注意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的請(qǐng)求,都是因?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ā)送請(qǐng)求的
    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
}

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

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

// 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í)之后,請(qǐng)求依然會(huì)繼續(xù)等待返回值,只是返回值會(huì)被丟棄,如果請(qǐng)求時(shí)間太長(zhǎng),會(huì)把連接池占滿,正好是我們遇到的問題。為了解決,需要設(shè)置ReadTimeout字段,這個(gè)字段的我個(gè)人理解的意思就是當(dāng)請(qǐng)求發(fā)出之后,達(dá)到ReadTimeout時(shí)間還沒有得到返回值,客戶端就會(huì)把連接斷開(釋放)。

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

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

相關(guān)文章

  • 如何編寫Go語言中間件的實(shí)例教程

    如何編寫Go語言中間件的實(shí)例教程

    不知道大家有沒有寫過中間件呢,它是怎么寫的呢?下面這篇文中就來給大家分享一下使用Go,如何編寫中間件,文中通過示例代碼介紹的非常詳細(xì),供大家參考學(xué)習(xí),下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2018-04-04
  • 淺析GO并發(fā)處理選擇sync還是channel

    淺析GO并發(fā)處理選擇sync還是channel

    這篇文章主要想來和大家討論一下,GO?語言處理并發(fā)的時(shí)候我們是選擇sync還是channel,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下
    2023-08-08
  • Go語言中使用反射的方法

    Go語言中使用反射的方法

    這篇文章主要介紹了Go語言中使用反射的方法,實(shí)例分析了Go語言實(shí)現(xiàn)反射的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-02-02
  • 深入Golang的接口interface

    深入Golang的接口interface

    這篇文章主要介紹了深入Golang的接口interface,go不要求類型顯示地聲明實(shí)現(xiàn)了哪個(gè)接口,只要實(shí)現(xiàn)了相關(guān)的方法即可,編譯器就能檢測(cè)到,接下來關(guān)于接口interface的相關(guān)介紹需要的朋友可以參考下面文章內(nèi)容
    2022-06-06
  • Go語言同步與異步執(zhí)行多個(gè)任務(wù)封裝詳解(Runner和RunnerAsync)

    Go語言同步與異步執(zhí)行多個(gè)任務(wù)封裝詳解(Runner和RunnerAsync)

    這篇文章主要給大家介紹了關(guān)于Go語言同步與異步執(zhí)行多個(gè)任務(wù)封裝(Runner和RunnerAsync)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2018-01-01
  • Redis?BloomFilter布隆過濾器原理與實(shí)現(xiàn)

    Redis?BloomFilter布隆過濾器原理與實(shí)現(xiàn)

    你在開發(fā)或者面試過程中,有沒有遇到過?海量數(shù)據(jù)需要查重,緩存穿透怎么避免等等這樣的問題呢?下面這個(gè)東西超棒,好好了解下,面試過關(guān)斬將,凸顯你的不一樣
    2022-10-10
  • Go語言編程中字符串切割方法小結(jié)

    Go語言編程中字符串切割方法小結(jié)

    這篇文章主要介紹了Go語言編程中字符串切割方法小結(jié),所整理的方法都來自字符串相關(guān)的strings包,需要的朋友可以參考下
    2015-10-10
  • Go并發(fā)編程結(jié)構(gòu)體多字段原子操作示例詳解

    Go并發(fā)編程結(jié)構(gòu)體多字段原子操作示例詳解

    這篇文章主要為大家介紹了Go并發(fā)編程結(jié)構(gòu)體多字段原子操作示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • Go語言中掃描Redis中大量key的示例代碼

    Go語言中掃描Redis中大量key的示例代碼

    在 Redis 中,當(dāng)我們需要遍歷大量的鍵時(shí),直接使用 KEYS 命令會(huì)面臨性能瓶頸,尤其是在鍵數(shù)量非常多的情況下,今天,我們將通過兩個(gè)示例代碼,詳細(xì)講解如何在 Go 語言中使用 SCAN 命令遍歷 Redis 鍵,需要的朋友可以參考下
    2024-08-08
  • 一起聊聊Go語言中的語法糖的使用

    一起聊聊Go語言中的語法糖的使用

    語法糖通常是用來簡(jiǎn)化代碼編寫的,特性就是使用語法糖前后編譯的結(jié)果是相同的。這篇文章主要就來和大家一起聊聊Go語言中的語法糖的實(shí)現(xiàn)
    2022-07-07

最新評(píng)論

关岭| 潞西市| 荣昌县| 大田县| 博乐市| 邛崃市| 大同市| 武陟县| 台前县| 扎兰屯市| 双辽市| 门头沟区| 德清县| 紫云| 班戈县| 平定县| 淮阳县| 乌兰察布市| 雅江县| 四川省| 若尔盖县| 玉门市| 柳林县| 军事| 龙岩市| 金塔县| 东莞市| 海原县| 襄汾县| 上林县| 河曲县| 绥中县| 时尚| 沂南县| 手游| 桐梓县| 隆尧县| 郸城县| 永仁县| 德惠市| 台湾省|