<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          總是收到 i/o timeout 報(bào)錯(cuò),這里有 HTTP 的避坑指南!

          共 22365字,需瀏覽 45分鐘

           ·

          2021-05-14 17:19

          點(diǎn)擊上方“服務(wù)端思維”,選擇“設(shè)為星標(biāo)

          回復(fù)”669“獲取獨(dú)家整理的精選資料集

          回復(fù)”加群“加入全國(guó)服務(wù)端高端社群「后端圈」


          作者 | 詠春警告的胖虎
          出品 | golang小白成長(zhǎng)記

          問(wèn)題

          我們來(lái)看一段日常代碼。

           1package main
          2
          3import (
          4    "bytes"
          5    "encoding/json"
          6    "fmt"
          7    "io/ioutil"
          8    "net"
          9    "net/http"
          10    "time"
          11)
          12
          13var tr *http.Transport
          14
          15func init() {
          16    tr = &http.Transport{
          17        MaxIdleConns: 100,
          18        Dial: func(netw, addr string) (net.Conn, error) {
          19            conn, err := net.DialTimeout(netw, addr, time.Second*2//設(shè)置建立連接超時(shí)
          20            if err != nil {
          21                return nil, err
          22            }
          23            err = conn.SetDeadline(time.Now().Add(time.Second * 3)) //設(shè)置發(fā)送接受數(shù)據(jù)超時(shí)
          24            if err != nil {
          25                return nil, err
          26            }
          27            return conn, nil
          28        },
          29    }
          30}
          31
          32func main() {
          33    for {
          34        _, err := Get("http://www.baidu.com/")
          35        if err != nil {
          36            fmt.Println(err)
          37            break
          38        }
          39    }
          40}
          41
          42
          43func Get(url string) ([]byte, error) {
          44    m := make(map[string]interface{})
          45    data, err := json.Marshal(m)
          46    if err != nil {
          47        return nil, err
          48    }
          49    body := bytes.NewReader(data)
          50    req, _ := http.NewRequest("Get", url, body)
          51    req.Header.Add("content-type""application/json")
          52
          53    client := &http.Client{
          54        Transport: tr,
          55    }
          56    res, err := client.Do(req)
          57    if res != nil {
          58        defer res.Body.Close()
          59    }
          60    if err != nil {
          61        return nil, err
          62    }
          63    resBody, err := ioutil.ReadAll(res.Body)
          64    if err != nil {
          65        return nil, err
          66    }
          67    return resBody, nil
          68}

          做的事情,比較簡(jiǎn)單,就是循環(huán)去請(qǐng)求 http://www.baidu.com/ , 然后等待響應(yīng)。

          看上去貌似沒(méi)啥問(wèn)題吧。

          代碼跑起來(lái),也確實(shí)能正常收發(fā)消息。

          但是這段代碼跑一段時(shí)間,就會(huì)出現(xiàn) i/o timeout 的報(bào)錯(cuò)。


          這其實(shí)是最近排查了的一個(gè)問(wèn)題,發(fā)現(xiàn)這個(gè)坑可能比較容易踩上,我這邊對(duì)代碼做了簡(jiǎn)化。

          實(shí)際生產(chǎn)中發(fā)生的現(xiàn)象是,golang服務(wù)在發(fā)起http調(diào)用時(shí),雖然http.Transport設(shè)置了3s超時(shí),會(huì)偶發(fā)出現(xiàn)i/o timeout的報(bào)錯(cuò)。

          但是查看下游服務(wù)的時(shí)候,發(fā)現(xiàn)下游服務(wù)其實(shí) 100ms 就已經(jīng)返回了。


          排查

          五層網(wǎng)絡(luò)協(xié)議對(duì)應(yīng)的消息體變化分析

          就很奇怪了,明明服務(wù)端顯示處理耗時(shí)才100ms,且客戶端超時(shí)設(shè)的是3s, 怎么就出現(xiàn)超時(shí)報(bào)錯(cuò) i/o timeout 呢?

          這里推測(cè)有兩個(gè)可能。

          • 因?yàn)榉?wù)端打印的日志其實(shí)只是服務(wù)端應(yīng)用層打印的日志。但客戶端應(yīng)用層發(fā)出數(shù)據(jù)后,中間還經(jīng)過(guò)客戶端的傳輸層,網(wǎng)絡(luò)層,數(shù)據(jù)鏈路層和物理層,再經(jīng)過(guò)服務(wù)端的物理層,數(shù)據(jù)鏈路層,網(wǎng)絡(luò)層,傳輸層到服務(wù)端的應(yīng)用層。服務(wù)端應(yīng)用層處耗時(shí)100ms,再原路返回。那剩下的3s-100ms可能是耗在了整個(gè)流程里的各個(gè)層上。比如網(wǎng)絡(luò)不好的情況下,傳輸層TCP使勁丟包重傳之類(lèi)的原因。

          • 網(wǎng)絡(luò)沒(méi)問(wèn)題,客戶端到服務(wù)端鏈路整個(gè)收發(fā)流程大概耗時(shí)就是100ms左右??蛻舳颂幚磉壿媶?wèn)題導(dǎo)致超時(shí)。

          一般遇到問(wèn)題,大部分情況下都不會(huì)是底層網(wǎng)絡(luò)的問(wèn)題,大膽懷疑是自己的問(wèn)題就對(duì)了,不死心就抓個(gè)包看下。

          抓包結(jié)果

          分析下,從剛開(kāi)始三次握手(畫(huà)了紅框的地方)。

          到最后出現(xiàn)超時(shí)報(bào)錯(cuò) i/o timeout畫(huà)了藍(lán)框的地方)。

          time那一列從710,確實(shí)間隔3s。而且看右下角的藍(lán)框,是51169端口發(fā)到80端口的一次Reset連接。

          80端口是服務(wù)端的端口。換句話說(shuō)就是客戶端3s超時(shí)主動(dòng)斷開(kāi)鏈接的。

          但是再仔細(xì)看下第一行三次握手到最后客戶端超時(shí)主動(dòng)斷開(kāi)連接的中間,其實(shí)有非常多次HTTP請(qǐng)求。

          回去看代碼設(shè)置超時(shí)的方式。

           1    tr = &http.Transport{
          2        MaxIdleConns: 100,
          3        Dial: func(netw, addr string) (net.Conn, error) {
          4            conn, err := net.DialTimeout(netw, addr, time.Second*2//設(shè)置建立連接超時(shí)
          5            if err != nil {
          6                return nil, err
          7            }
          8            err = conn.SetDeadline(time.Now().Add(time.Second * 3)) //設(shè)置發(fā)送接受數(shù)據(jù)超時(shí)
          9            if err != nil {
          10                return nil, err
          11            }
          12            return conn, nil
          13        },
          14    }

          也就是說(shuō),這里的3s超時(shí),其實(shí)是在建立連接之后開(kāi)始算的,而不是單次調(diào)用開(kāi)始算的超時(shí)。

          看注釋里寫(xiě)的是

          SetDeadline sets the read and write deadlines associated with the connection.

          超時(shí)原因

          大家知道HTTP是應(yīng)用層協(xié)議,傳輸層用的是TCP協(xié)議。

          HTTP協(xié)議從1.0以前,默認(rèn)用的是短連接,每次發(fā)起請(qǐng)求都會(huì)建立TCP連接。收發(fā)數(shù)據(jù)。然后斷開(kāi)連接。

          TCP連接每次都是三次握手。每次斷開(kāi)都要四次揮手。

          其實(shí)沒(méi)必要每次都建立新連接,建立的連接不斷開(kāi)就好了,每次發(fā)送數(shù)據(jù)都復(fù)用就好了。

          于是乎,HTTP協(xié)議從1.1之后就默認(rèn)使用長(zhǎng)連接。具體相關(guān)信息可以看之前的 這篇文章。

          那么golang標(biāo)準(zhǔn)庫(kù)里也兼容這種實(shí)現(xiàn)。

          通過(guò)建立一個(gè)連接池,針對(duì)每個(gè)域名建立一個(gè)TCP長(zhǎng)連接,比如http://baidu.comhttp://golang.com 就是兩個(gè)不同的域名。

          第一次訪問(wèn)http://baidu.com 域名的時(shí)候會(huì)建立一個(gè)連接,用完之后放到空閑連接池里,下次再要訪問(wèn)http://baidu.com 的時(shí)候會(huì)重新從連接池里把這個(gè)連接撈出來(lái)復(fù)用。

          復(fù)用長(zhǎng)連接

          插個(gè)題外話:這也解釋了之前這篇文章里最后的疑問(wèn),為什么要強(qiáng)調(diào)是同一個(gè)域名:一個(gè)域名會(huì)建立一個(gè)連接,一個(gè)連接對(duì)應(yīng)一個(gè)讀goroutine和一個(gè)寫(xiě)goroutine。正因?yàn)槭峭粋€(gè)域名,所以最后才會(huì)泄漏3個(gè)goroutine,如果不同域名的話,那就會(huì)泄漏 1+2*N 個(gè)協(xié)程,N就是域名數(shù)。

          假設(shè)第一次請(qǐng)求要100ms,每次請(qǐng)求完http://baidu.com 后都放入連接池中,下次繼續(xù)復(fù)用,重復(fù)29次,耗時(shí)2900ms。

          30次請(qǐng)求的時(shí)候,連接從建立開(kāi)始到服務(wù)返回前就已經(jīng)用了3000ms,剛好到設(shè)置的3s超時(shí)閾值,那么此時(shí)客戶端就會(huì)報(bào)超時(shí) i/o timeout 。

          雖然這時(shí)候服務(wù)端其實(shí)才花了100ms,但耐不住前面29次加起來(lái)的耗時(shí)已經(jīng)很長(zhǎng)。

          也就是說(shuō)只要通過(guò) http.Transport 設(shè)置了 err = conn.SetDeadline(time.Now().Add(time.Second * 3)),并且你用了長(zhǎng)連接,哪怕服務(wù)端處理再快,客戶端設(shè)置的超時(shí)再長(zhǎng),總有一刻,你的程序會(huì)報(bào)超時(shí)錯(cuò)誤。

          正確姿勢(shì)

          原本預(yù)期是給每次調(diào)用設(shè)置一個(gè)超時(shí),而不是給整個(gè)連接設(shè)置超時(shí)。

          另外,上面出現(xiàn)問(wèn)題的原因是給長(zhǎng)連接設(shè)置了超時(shí),且長(zhǎng)連接會(huì)復(fù)用。

          基于這兩點(diǎn),改一下代碼。

           1package main
          2
          3import (
          4    "bytes"
          5    "encoding/json"
          6    "fmt"
          7    "io/ioutil"
          8    "net/http"
          9    "time"
          10)
          11
          12var tr *http.Transport
          13
          14func init() {
          15    tr = &http.Transport{
          16        MaxIdleConns: 100,
          17        // 下面的代碼被干掉了
          18        //Dial: func(netw, addr string) (net.Conn, error) {
          19        //  conn, err := net.DialTimeout(netw, addr, time.Second*2) //設(shè)置建立連接超時(shí)
          20        //  if err != nil {
          21        //      return nil, err
          22        //  }
          23        //  err = conn.SetDeadline(time.Now().Add(time.Second * 3)) //設(shè)置發(fā)送接受數(shù)據(jù)超時(shí)
          24        //  if err != nil {
          25        //      return nil, err
          26        //  }
          27        //  return conn, nil
          28        //},
          29    }
          30}
          31
          32
          33func Get(url string) ([]byte, error) {
          34    m := make(map[string]interface{})
          35    data, err := json.Marshal(m)
          36    if err != nil {
          37        return nil, err
          38    }
          39    body := bytes.NewReader(data)
          40    req, _ := http.NewRequest("Get", url, body)
          41    req.Header.Add("content-type""application/json")
          42
          43    client := &http.Client{
          44        Transport: tr,
          45        Timeout: 3*time.Second,  // 超時(shí)加在這里,是每次調(diào)用的超時(shí)
          46    }
          47    res, err := client.Do(req) 
          48    if res != nil {
          49        defer res.Body.Close()
          50    }
          51    if err != nil {
          52        return nil, err
          53    }
          54    resBody, err := ioutil.ReadAll(res.Body)
          55    if err != nil {
          56        return nil, err
          57    }
          58    return resBody, nil
          59}
          60
          61func main() {
          62    for {
          63        _, err := Get("http://www.baidu.com/")
          64        if err != nil {
          65            fmt.Println(err)
          66            break
          67        }
          68    }
          69}

          看注釋會(huì)發(fā)現(xiàn),改動(dòng)的點(diǎn)有兩個(gè)

          • http.Transport里的建立連接時(shí)的一些超時(shí)設(shè)置干掉了。

          • 在發(fā)起http請(qǐng)求的時(shí)候會(huì)場(chǎng)景http.Client,此時(shí)加入超時(shí)設(shè)置,這里的超時(shí)就可以理解為單次請(qǐng)求的超時(shí)了。同樣可以看下注釋

          Timeout specifies a time limit for requests made by this Client.

          到這里,代碼就改好了,實(shí)際生產(chǎn)中問(wèn)題也就解決了。

          實(shí)例代碼里,如果拿去跑的話,其實(shí)還會(huì)下面的錯(cuò)

          1Get http://www.baidu.com/: EOF

          這個(gè)是因?yàn)檎{(diào)用得太猛了,http://www.baidu.com 那邊主動(dòng)斷開(kāi)的連接,可以理解為一個(gè)限流措施,目的是為了保護(hù)服務(wù)器,畢竟每個(gè)人都像這么搞,服務(wù)器是會(huì)炸的。。。

          解決方案很簡(jiǎn)單,每次HTTP調(diào)用中間加個(gè)sleep間隔時(shí)間就好。

          到這里,其實(shí)問(wèn)題已經(jīng)解決了,下面會(huì)在源碼層面分析出現(xiàn)問(wèn)題的原因。對(duì)讀源碼不感興趣的朋友們可以不用接著往下看,直接拉到文章底部右下角,做點(diǎn)正能量的事情(點(diǎn)兩下)支持一下。(瘋狂暗示,拜托拜托,這對(duì)我真的很重要!

          源碼分析

          用的go版本是1.12.7

          從發(fā)起一個(gè)網(wǎng)絡(luò)請(qǐng)求開(kāi)始跟。

           1res, err := client.Do(req)
          2func (c *Client) Do(req *Request) (*Response, error) {
          3    return c.do(req)
          4}
          5
          6func (c *Client) do(req *Request) {
          7    // ...
          8    if resp, didTimeout, err = c.send(req, deadline); err != nil {
          9    // ...
          10  }
          11    // ...  
          12}  
          13func send(ireq *Request, rt RoundTripper, deadline time.Time) {
          14    // ...    
          15    resp, err = rt.RoundTrip(req)
          16     // ...  
          17
          18
          19// 從這里進(jìn)入 RoundTrip 邏輯
          20/src/net/http/roundtrip.go16
          21func (t *Transport) RoundTrip(req *Request) (*Response, error) {
          22    return t.roundTrip(req)
          23}
          24
          25func (t *Transport) roundTrip(req *Request) (*Response, error) {
          26    // 嘗試去獲取一個(gè)空閑連接,用于發(fā)起 http 連接
          27  pconn, err := t.getConn(treq, cm)
          28  // ...
          29}
          30
          31// 重點(diǎn)關(guān)注這個(gè)函數(shù),返回是一個(gè)長(zhǎng)連接
          32func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (*persistConn, error) {
          33  // 省略了大量邏輯,只關(guān)注下面兩點(diǎn)
          34    // 有空閑連接就返回
          35    pc := <-t.getIdleConnCh(cm)
          36
          37  // 沒(méi)有創(chuàng)建連接
          38  pc, err := t.dialConn(ctx, cm)
          39
          40}

          這里上面很多代碼,其實(shí)只是為了展示這部分代碼是怎么跟蹤下來(lái)的,方便大家去看源碼的時(shí)候去跟一下。

          最后一個(gè)上面的代碼里有個(gè) getConn 方法。在發(fā)起網(wǎng)絡(luò)請(qǐng)求的時(shí)候,會(huì)先取一個(gè)網(wǎng)絡(luò)連接,取連接有兩個(gè)來(lái)源。

          • 如果有空閑連接,就拿空閑連接

            1/src/net/http/tansport.go:810
            2func (t *Transport) getIdleConnCh(cm connectMethod) chan *persistConn {
            3 // 返回放空閑連接的chan
            4 ch, ok := t.idleConnCh[key]
            5   // ...
            6 return ch
            7}
          • 沒(méi)有空閑連接,就創(chuàng)建長(zhǎng)連接。

          1/src/net/http/tansport.go:1357
          2func (t *Transport) dialConn() {
          3  //...
          4  conn, err := t.dial(ctx, "tcp", cm.addr())
          5  // ...
          6  go pconn.readLoop()
          7  go pconn.writeLoop()
          8  // ...
          9}

          當(dāng)第一次發(fā)起一個(gè)http請(qǐng)求時(shí),這時(shí)候肯定沒(méi)有空閑連接,會(huì)建立一個(gè)新連接。同時(shí)會(huì)創(chuàng)建一個(gè)讀goroutine和一個(gè)寫(xiě)goroutine。

          讀寫(xiě)協(xié)程

          注意上面代碼里的t.dial(ctx, "tcp", cm.addr()),如果像文章開(kāi)頭那樣設(shè)置了 http.Transport

           1Dial: func(netw, addr string) (net.Conn, error) {
          2   conn, err := net.DialTimeout(netw, addr, time.Second*2//設(shè)置建立連接超時(shí)
          3   if err != nil {
          4      return nil, err
          5   }
          6   err = conn.SetDeadline(time.Now().Add(time.Second * 3)) //設(shè)置發(fā)送接受數(shù)據(jù)超時(shí)
          7   if err != nil {
          8      return nil, err
          9   }
          10   return conn, nil
          11},

          那么這里就會(huì)在下面的dial里被執(zhí)行到

          1func (t *Transport) dial(ctx context.Context, network, addr string) (net.Conn, error) {
          2   // ...
          3  c, err := t.Dial(network, addr)
          4  // ...
          5}

          這里面調(diào)用的設(shè)置超時(shí),會(huì)執(zhí)行到

           1/src/net/net.go
          2func (c *conn) SetDeadline(t time.Time) error {
          3    //...
          4    c.fd.SetDeadline(t)
          5    //...
          6}
          7
          8//...
          9
          10func setDeadlineImpl(fd *FD, t time.Time, mode int) error {
          11    // ...
          12    runtime_pollSetDeadline(fd.pd.runtimeCtx, d, mode)
          13    return nil
          14}
          15
          16
          17//go:linkname poll_runtime_pollSetDeadline internal/poll.runtime_pollSetDeadline
          18func poll_runtime_pollSetDeadline(pd *pollDesc, d int64, mode int) {
          19    // ...
          20  // 設(shè)置一個(gè)定時(shí)器事件
          21  rtf = netpollDeadline
          22    // 并將事件注冊(cè)到定時(shí)器里
          23  modtimer(&pd.rt, pd.rd, 0, rtf, pd, pd.rseq)
          24}  

          上面的源碼,簡(jiǎn)單來(lái)說(shuō)就是,當(dāng)?shù)谝淮握{(diào)用請(qǐng)求的,會(huì)建立個(gè)連接,這時(shí)候還會(huì)注冊(cè)一個(gè)定時(shí)器事件,假設(shè)時(shí)間設(shè)了3s,那么這個(gè)事件會(huì)在3s后發(fā)生,然后執(zhí)行注冊(cè)事件的邏輯。而這個(gè)注冊(cè)事件就是netpollDeadline注意這個(gè)netpollDeadline,待會(huì)會(huì)提到。

          讀寫(xiě)協(xié)程定時(shí)器事件

          設(shè)置了超時(shí)事件,且超時(shí)事件是3s后之后,發(fā)生。再次期間正常收發(fā)數(shù)據(jù)。一切如常。

          直到3s過(guò)后,這時(shí)候看讀goroutine,會(huì)等待網(wǎng)絡(luò)數(shù)據(jù)返回。

          1/src/net/http/tansport.go:1642
          2func (pc *persistConn) readLoop() {
          3    //...
          4    for alive {
          5        _, err := pc.br.Peek(1)  // 阻塞讀取服務(wù)端返回的數(shù)據(jù)
          6    //...
          7}

          然后就是一直跟代碼。

           1src/bufio/bufio.go129
          2func (b *Reader) Peek(n int) ([]byte, error) {
          3   // ...
          4   b.fill() 
          5   // ...   
          6}
          7
          8func (b *Reader) fill() {
          9    // ...
          10    n, err := b.rd.Read(b.buf[b.w:])
          11    // ...
          12}
          13
          14/src/net/http/transport.go1517
          15func (pc *persistConn) Read(p []byte) (n int, err error) {
          16    // ...
          17    n, err = pc.conn.Read(p)
          18    // ...
          19}
          20
          21// /src/net/net.go: 173
          22func (c *conn) Read(b []byte) (int, error) {
          23    // ...
          24    n, err := c.fd.Read(b)
          25    // ...
          26}
          27
          28func (fd *netFD) Read(p []byte) (n int, err error) {
          29    n, err = fd.pfd.Read(p)
          30    // ...
          31}
          32
          33/src/internal/poll/fd_unix.go
          34func (fd *FD) Read(p []byte) (int, error) {
          35    //...
          36  if err = fd.pd.waitRead(fd.isFile); err == nil {
          37    continue
          38  }
          39    // ...
          40}
          41
          42func (pd *pollDesc) waitRead(isFile bool) error {
          43    return pd.wait('r', isFile)
          44}
          45
          46func (pd *pollDesc) wait(mode int, isFile bool) error {
          47    // ...
          48  res := runtime_pollWait(pd.runtimeCtx, mode)
          49    return convertErr(res, isFile)
          50}

          直到跟到 runtime_pollWait,這個(gè)可以簡(jiǎn)單認(rèn)為是等待服務(wù)端數(shù)據(jù)返回。

           1//go:linkname poll_runtime_pollWait internal/poll.runtime_pollWait
          2func poll_runtime_pollWait(pd *pollDesc, mode int) int {
          3
          4    // 1.如果網(wǎng)絡(luò)正常返回?cái)?shù)據(jù)就跳出
          5  for !netpollblock(pd, int32(mode), false) {
          6    // 2.如果有出錯(cuò)情況也跳出
          7        err = netpollcheckerr(pd, int32(mode))
          8        if err != 0 {
          9            return err
          10        }
          11    }
          12    return 0
          13}

          整條鏈路跟下來(lái),就是會(huì)一直等待數(shù)據(jù),等待的結(jié)果只有兩個(gè)

          • 有可以讀的數(shù)據(jù)

          • 出現(xiàn)報(bào)錯(cuò)

          這里面的報(bào)錯(cuò),又有那么兩種

          • 連接關(guān)閉

          • 超時(shí)

          1func netpollcheckerr(pd *pollDesc, mode int32) int {
          2    if pd.closing {
          3        return 1 // errClosing
          4    }
          5    if (mode == 'r' && pd.rd < 0) || (mode == 'w' && pd.wd < 0) {
          6        return 2 // errTimeout
          7    }
          8    return 0
          9}

          其中提到的超時(shí),就是指這里面返回的數(shù)字2,會(huì)通過(guò)下面的函數(shù),轉(zhuǎn)化為 ErrTimeout, 而 ErrTimeout.Error() 其實(shí)就是 i/o timeout。

           1func convertErr(res int, isFile bool) error {
          2    switch res {
          3    case 0:
          4        return nil
          5    case 1:
          6        return errClosing(isFile)
          7    case 2:
          8        return ErrTimeout // ErrTimeout.Error() 就是 "i/o timeout"
          9    }
          10    println("unreachable: ", res)
          11    panic("unreachable")
          12}

          那么問(wèn)題來(lái)了。上面返回的超時(shí)錯(cuò)誤,也就是返回2的時(shí)候的條件是怎么滿足的?

          1    if (mode == 'r' && pd.rd < 0) || (mode == 'w' && pd.wd < 0) {
          2        return 2 // errTimeout
          3    }

          還記得剛剛提到的 netpollDeadline嗎?

          這里面放了定時(shí)器3s到點(diǎn)時(shí)執(zhí)行的邏輯。

           1func timerproc(tb *timersBucket) {
          2    // 計(jì)時(shí)器到設(shè)定時(shí)間點(diǎn)了,觸發(fā)之前注冊(cè)函數(shù)
          3    f(arg, seq) // 之前注冊(cè)的是 netpollDeadline
          4}
          5
          6func netpollDeadline(arg interface{}, seq uintptr) {
          7    netpolldeadlineimpl(arg.(*pollDesc), seq, truetrue)
          8}
          9
          10/src/runtime/netpoll.go428
          11func netpolldeadlineimpl(pd *pollDesc, seq uintptr, read, write bool) {
          12    //...
          13    if read {
          14        pd.rd = -1
          15        rg = netpollunblock(pd, 'r'false)
          16    }
          17    //...
          18}

          這里會(huì)設(shè)置pd.rd=-1,是指 poller descriptor.read deadline ,含義網(wǎng)絡(luò)輪詢器文件描述符讀超時(shí)時(shí)間, 我們知道在linux里萬(wàn)物皆文件,這里的文件其實(shí)是指這次網(wǎng)絡(luò)通訊中使用到的socket。

          這時(shí)候再回去看發(fā)生超時(shí)的條件就是if (mode == 'r' && pd.rd < 0)。

          至此。我們的代碼里就收到了 io timeout 的報(bào)錯(cuò)。

          總結(jié)

          • 不要在 http.Transport中設(shè)置超時(shí),那是連接的超時(shí),不是請(qǐng)求的超時(shí)。否則可能會(huì)出現(xiàn)莫名 io timeout報(bào)錯(cuò)。

          • 請(qǐng)求的超時(shí)在創(chuàng)建client里設(shè)置。

          如果文章對(duì)你有幫助,看下文章底部右下角,做點(diǎn)正能量的事情(點(diǎn)兩下)支持一下。(瘋狂暗示,拜托拜托,這對(duì)我真的很重要!

          我是小白,我們下期見(jiàn)。

          — 本文結(jié)束 —


          ● 漫談設(shè)計(jì)模式在 Spring 框架中的良好實(shí)踐

          ● 顛覆微服務(wù)認(rèn)知:深入思考微服務(wù)的七個(gè)主流觀點(diǎn)

          ● 人人都是 API 設(shè)計(jì)者

          ● 一文講透微服務(wù)下如何保證事務(wù)的一致性

          ● 要黑盒測(cè)試微服務(wù)內(nèi)部服務(wù)間調(diào)用,我該如何實(shí)現(xiàn)?



          關(guān)注我,回復(fù) 「加群」 加入各種主題討論群。



          對(duì)「服務(wù)端思維」有期待,請(qǐng)?jiān)谖哪c(diǎn)個(gè)在看

          喜歡這篇文章,歡迎轉(zhuǎn)發(fā)、分享朋友圈


          在看點(diǎn)這里
          瀏覽 94
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲激情春色 | www亚洲无 码A片 | 精品小视频| 久九九久国产精品 | 欧美激情爱爱网址 |