grpc 超時(shí)和重連

最近項(xiàng)目要使用grpc,但是關(guān)于grpc的超時(shí)和重連這一塊很多文章都是說的不夠詳細(xì),無奈只能自己看代碼.順手記錄一下。

超時(shí)

建立連接

主要就2函數(shù)Dail和DialContext阱州。

// Dial creates a client connection to the given target.
func Dial(target string, opts ...DialOption) (*ClientConn, error) {
    return DialContext(context.Background(), target, opts...)
}
func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *ClientConn, err error){...}

DialContext 太長(zhǎng)了不帖了.看Dial實(shí)際上也是調(diào)用DialContext來實(shí)現(xiàn)的.如果你想在建立連接的時(shí)候使用超時(shí)控制.就使用DialContext傳入一個(gè)Timeout的context,就像下面的例子

ctx1, cel := context.WithTimeout(context.Background(), time.Second*3)
defer cel()
conn, err := grpc.DialContext(ctx1, address, grpc.WithBlock(), grpc.WithInsecure())

另外調(diào)用Dial建立連接默認(rèn)只是返回一個(gè)ClientConn的指針,相當(dāng)于new了一個(gè)ClientConn 把指針返回給你熔酷。并不是一定要建立真實(shí)的h2連接.至于真實(shí)的連接建立實(shí)際上是一個(gè)異步的過程珍促。當(dāng)然了如果你想等真實(shí)的鏈接完全建立再返回ClientConn可以通過WithBlock傳入Options來實(shí)現(xiàn),當(dāng)然了這樣的話鏈接如果建立不成功就會(huì)一直阻塞直到Contex超時(shí)燃乍。真正的建立鏈接的代碼后面介紹重試的時(shí)候會(huì)再詳細(xì)介紹。

調(diào)用超時(shí)

這個(gè)比較簡(jiǎn)單

ctx, cancel := context.WithTimeout(context.TODO(), time.Second*3)
defer cancel()
 r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})

如上代碼傳入一個(gè)timeout context就可以杈笔。

重連

假設(shè)我們想這樣一個(gè)問題,剛才我們說Dial實(shí)際上是new了一個(gè)ClientConn.真實(shí)的連接建立在另外一個(gè)協(xié)程中,那這個(gè)協(xié)程是建立連接后就退出了呢,還是還在運(yùn)行挑童。另外如果我們退出服務(wù)端然后啟動(dòng)客戶端會(huì)重新建立鏈接嗎,如果是那又是如何重試的累铅。

grpc調(diào)用的時(shí)候啟動(dòng)的協(xié)程

要回答第一個(gè)問題,很簡(jiǎn)單我們?cè)赾lient代碼中啟動(dòng)pprof看看有哪些協(xié)程在跑。

   go func() {
        log.Println(http.ListenAndServe("localhost:6006", nil))
    }()
main.main.func1()
    /Users/myonlyzzy/go/src/google.golang.org/grpc/examples/helloworld/greeter_client/main.go:41 +0x3e
created by main.main
    /Users/myonlyzzy/go/src/google.golang.org/grpc/examples/helloworld/greeter_client/main.go:40 +0x47

goroutine 6 [select]:
google.golang.org/grpc.(*ccResolverWrapper).watcher(0xc4201941e0)
    /Users/myonlyzzy/go/src/google.golang.org/grpc/resolver_conn_wrapper.go:110 +0x182
created by google.golang.org/grpc.(*ccResolverWrapper).start
    /Users/myonlyzzy/go/src/google.golang.org/grpc/resolver_conn_wrapper.go:96 +0x3f

goroutine 7 [select]:
google.golang.org/grpc.(*ccBalancerWrapper).watcher(0xc42006e280)
    /Users/myonlyzzy/go/src/google.golang.org/grpc/balancer_conn_wrappers.go:122 +0x14a
created by google.golang.org/grpc.newCCBalancerWrapper
    /Users/myonlyzzy/go/src/google.golang.org/grpc/balancer_conn_wrappers.go:113 +0x14c

goroutine 8 [select]:
google.golang.org/grpc.(*addrConn).transportMonitor(0xc42019e280)
    /Users/myonlyzzy/go/src/google.golang.org/grpc/clientconn.go:1240 +0x235
google.golang.org/grpc.(*addrConn).connect.func1(0xc42019e280)
    /Users/myonlyzzy/go/src/google.golang.org/grpc/clientconn.go:839 +0x216
created by google.golang.org/grpc.(*addrConn).connect
    /Users/myonlyzzy/go/src/google.golang.org/grpc/clientconn.go:829 +0xe1

我們看到有一個(gè)transportMonitor的協(xié)程一直阻塞在select中.代碼都在clientconn.go 中站叼。我們進(jìn)去看看其實(shí)有4個(gè)主要的方法.

func (ac *addrConn) connect() error 
func (ac *addrConn) resetTransport() error
func (ac *addrConn) createTransport(connectRetryNum, ridx int, backoffDeadline, connectDeadline time.Time, addrs []resolver.Address, copts transport.ConnectOptions) (bool, error)
func (ac *addrConn) transportMonitor()

connect

    // Start a goroutine connecting to the server asynchronously.
    go func() {
        if err := ac.resetTransport(); err != nil {
            log.Printf("resetTransport %v ",err)
            grpclog.Warningf("Failed to dial %s: %v; please retry.", ac.addrs[0].Addr, err)
            if err != errConnClosing {
                // Keep this ac in cc.conns, to get the reason it's torn down.
                ac.tearDown(err)
            }
            return
        }
        ac.transportMonitor()
    }()
    return nil

上面的是connect的一部分娃兽。connect會(huì)調(diào)用resetTransport來建立鏈接。再啟動(dòng)transportMonitor來監(jiān)控鏈接的情況大年。

resetTransport

for connectRetryNum := 0; ; connectRetryNum++ {
       ac.mu.Lock()
       if ac.backoffDeadline.IsZero() {
           // This means either a successful HTTP2 connection was established
           // or this is the first time this addrConn is trying to establish a
           // connection.
           backoffFor := ac.dopts.bs.backoff(connectRetryNum) // time.Duration.
           // This will be the duration that dial gets to finish.
           dialDuration := minConnectTimeout
           if backoffFor > dialDuration {
               // Give dial more time as we keep failing to connect.
               dialDuration = backoffFor
           }
           start := time.Now()
           backoffDeadline = start.Add(backoffFor)
           connectDeadline = start.Add(dialDuration)
           ridx = 0 // Start connecting from the beginning.
       } else {
           // Continue trying to conect with the same deadlines.
           connectRetryNum = ac.connectRetryNum
           backoffDeadline = ac.backoffDeadline
           connectDeadline = ac.connectDeadline
           ac.backoffDeadline = time.Time{}
           ac.connectDeadline = time.Time{}
           ac.connectRetryNum = 0
       }
       if ac.state == connectivity.Shutdown {
           ac.mu.Unlock()
           return errConnClosing
       }
       ac.printf("connecting")
       if ac.state != connectivity.Connecting {
           ac.state = connectivity.Connecting
           ac.cc.handleSubConnStateChange(ac.acbw, ac.state)
       }
       // copy ac.addrs in case of race
       addrsIter := make([]resolver.Address, len(ac.addrs))
       copy(addrsIter, ac.addrs)
       copts := ac.dopts.copts
       ac.mu.Unlock()
       connected, err := ac.createTransport(connectRetryNum, ridx, backoffDeadline, connectDeadline, addrsIter, copts)
       if err != nil {
           return err
       }
       if connected {
           return nil
       }

   }

resetTransport 主要內(nèi)容就是一個(gè)for 循環(huán),可以看到在這個(gè)for循環(huán)中會(huì)嘗試建立鏈接换薄。如果建立成功就返回一個(gè)nil。如果不成功會(huì)不斷重試下去翔试。實(shí)際上不管是開頭的Dial或者Dial完了關(guān)閉服務(wù)器后都是由這段代碼來建立真實(shí)的鏈接轻要。這也就是如果你使用withBlock 但是不使用超時(shí)的話會(huì)不斷的重試下去。中途斷掉也會(huì)不斷重聯(lián)垦缅。當(dāng)然了重連的過程中是使用了backoff算法來重連冲泥。而且默認(rèn)會(huì)在grpc的配置中有個(gè)默認(rèn)最大重試間隔時(shí)間。默認(rèn)是120.

var DefaultBackoffConfig = BackoffConfig{
    MaxDelay:  120 * time.Second,
    baseDelay: 1.0 * time.Second,
    factor:    1.6,
    jitter:    0.2,
}

transportMonitor

for {
        var timer *time.Timer
        var cdeadline <-chan time.Time
        ac.mu.Lock()
        t := ac.transport
        if !ac.connectDeadline.IsZero() {
            timer = time.NewTimer(ac.connectDeadline.Sub(time.Now()))
            cdeadline = timer.C
        }
        ac.mu.Unlock()
        // Block until we receive a goaway or an error occurs.
        select {
        case <-t.GoAway():
        case <-t.Error():
        case <-cdeadline:
            ac.mu.Lock()
            // This implies that client received server preface.
            if ac.backoffDeadline.IsZero() {
                ac.mu.Unlock()
                continue
            }
            ac.mu.Unlock()
            timer = nil
            // No server preface received until deadline.
            // Kill the connection.
            grpclog.Warningf("grpc: addrConn.transportMonitor didn't get server preface after waiting. Closing the new transport now.")
            t.Close()
        }

        if timer != nil {
            timer.Stop()
        }
        // If a GoAway happened, regardless of error, adjust our keepalive
        // parameters as appropriate.
        select {
        case <-t.GoAway():
            ac.adjustParams(t.GetGoAwayReason())
        default:
        }
        ac.mu.Lock()
        if ac.state == connectivity.Shutdown {
            ac.mu.Unlock()
            return
        }
        // Set connectivity state to TransientFailure before calling
        // resetTransport. Transition READY->CONNECTING is not valid.
        ac.state = connectivity.TransientFailure
        ac.cc.handleSubConnStateChange(ac.acbw, ac.state)
        ac.cc.resolveNow(resolver.ResolveNowOption{})
        ac.curAddr = resolver.Address{}
        ac.mu.Unlock()
        if err := ac.resetTransport(); err != nil {
            ac.mu.Lock()
            ac.printf("transport exiting: %v", err)
            ac.mu.Unlock()
            grpclog.Warningf("grpc: addrConn.transportMonitor exits due to: %v", err)
            if err != errConnClosing {
                // Keep this ac in cc.conns, to get the reason it's torn down.
                ac.tearDown(err)
            }
            return
        }

    }

monitor也是運(yùn)行一個(gè)for 循環(huán)如果連接斷開就調(diào)用resetTransport重試壁涎。

其實(shí)我們使用etcdclient的時(shí)候的經(jīng)常要使用一個(gè)DialTimeout參數(shù)其實(shí)那個(gè)參數(shù)就是用來生成一個(gè)TimeOut的Context.用來控制建立鏈接的超時(shí)凡恍。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市怔球,隨后出現(xiàn)的幾起案子嚼酝,更是在濱河造成了極大的恐慌,老刑警劉巖竟坛,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闽巩,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡担汤,警方通過查閱死者的電腦和手機(jī)涎跨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來崭歧,“玉大人隅很,你說我怎么就攤上這事÷誓耄” “怎么了叔营?”我有些...
    開封第一講書人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)播掷。 經(jīng)常有香客問我审编,道長(zhǎng),這世上最難降的妖魔是什么歧匈? 我笑而不...
    開封第一講書人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任垒酬,我火速辦了婚禮,結(jié)果婚禮上件炉,老公的妹妹穿的比我還像新娘勘究。我一直安慰自己,他們只是感情好斟冕,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開白布口糕。 她就那樣靜靜地躺著,像睡著了一般磕蛇。 火紅的嫁衣襯著肌膚如雪景描。 梳的紋絲不亂的頭發(fā)上十办,一...
    開封第一講書人閱讀 51,562評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音超棺,去河邊找鬼向族。 笑死,一個(gè)胖子當(dāng)著我的面吹牛棠绘,可吹牛的內(nèi)容都是我干的件相。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼氧苍,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼夜矗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起让虐,我...
    開封第一講書人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤紊撕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后澄干,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體逛揩,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年麸俘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了辩稽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡从媚,死狀恐怖逞泄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情拜效,我是刑警寧澤喷众,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站紧憾,受9級(jí)特大地震影響到千,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜赴穗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一憔四、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧般眉,春花似錦了赵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至埠对,卻和暖如春络断,著一層夾襖步出監(jiān)牢的瞬間裁替,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工貌笨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留胯究,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓躁绸,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親臣嚣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子净刮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容