golang在GRPC中設(shè)置client的超時(shí)時(shí)間
超時(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來(lái)實(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 把指針?lè)祷亟o你。并不是一定要建立真實(shí)的h2連接.至于真實(shí)的連接建立實(shí)際上是一個(gè)異步的過(guò)程。
當(dāng)然了如果你想等真實(shí)的鏈接完全建立再返回ClientConn可以通過(guò)WithBlock傳入Options來(lái)實(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就可以。
Server
type SearchService struct{} func (s *SearchService) Search(ctx context.Context, r *pb.SearchRequest) (*pb.SearchResponse, error) { for i := 0; i < 5; i++ { if ctx.Err() == context.Canceled { return nil, status.Errorf(codes.Canceled, "SearchService.Search canceled") } time.Sleep(1 * time.Second) } return &pb.SearchResponse{Response: r.GetRequest() + " Server"}, nil } func main() { ... }
而在 Server 端,由于 Client 已經(jīng)設(shè)置了截止時(shí)間。Server 勢(shì)必要去檢測(cè)它
否則如果 Client 已經(jīng)結(jié)束掉了,Server 還傻傻的在那執(zhí)行,這對(duì)資源是一種極大的浪費(fèi)
因此在這里需要用 ctx.Err() == context.Canceled 進(jìn)行判斷,為了模擬場(chǎng)景我們加了循環(huán)和睡眠 ?
驗(yàn)證
重新啟動(dòng) server.go 和 client.go,得到結(jié)果:
$ go run client.go 2018/10/06 17:45:55 client.Search err: deadline exit status 1
總結(jié)
本章節(jié)比較簡(jiǎn)單,你需要知道以下知識(shí)點(diǎn):
怎么設(shè)置 Deadlines
為什么要設(shè)置 Deadlines
你要清楚地明白到,gRPC Deadlines 是很重要的,否則這小小的功能點(diǎn)就會(huì)要了你生產(chǎn)的命。
補(bǔ)充:golang使用grpc超時(shí)控制和對(duì)沖策略
超時(shí)控制
grcp超時(shí)控制設(shè)置在客戶端調(diào)用服務(wù)時(shí),如果設(shè)定了超時(shí)時(shí)間,客戶端會(huì)立即返回超時(shí)。超時(shí)控制一般有三個(gè)因素:鏈路超時(shí):上有調(diào)用端通過(guò)協(xié)議字段把自己允許的超時(shí)時(shí)間傳給當(dāng)前服務(wù),表示在該時(shí)間內(nèi)返回?cái)?shù)據(jù),超時(shí)返回已無(wú)意義。流程如下圖A調(diào)用B的總超時(shí)情況。
消息超時(shí):服務(wù)端收到請(qǐng)求消息到返回響應(yīng)數(shù)據(jù)的最長(zhǎng)消息處理時(shí)間。下圖的B內(nèi)部的當(dāng)前請(qǐng)求整體超時(shí)時(shí)間。調(diào)用超時(shí):當(dāng)前服務(wù)調(diào)用下游服務(wù)設(shè)置的每一個(gè)rpc請(qǐng)求的超時(shí)時(shí)間。如下圖B調(diào)用C的單個(gè)超時(shí)時(shí)間。通常一次請(qǐng)求會(huì)連續(xù)調(diào)用多次rpc,這個(gè)調(diào)用超時(shí)控制的是每個(gè)rpc的獨(dú)立超時(shí)時(shí)間。
發(fā)起rpc調(diào)用請(qǐng)求時(shí),需要計(jì)算此次rpc調(diào)用的超時(shí)時(shí)間。真正生效的超時(shí)時(shí)間是通過(guò)以上三個(gè)因素實(shí)時(shí)計(jì)算的最小值,計(jì)算過(guò)程
如下:
1、首先計(jì)算鏈路超時(shí)和消息超時(shí)的最小值,如鏈路超時(shí)2s,消息超時(shí)1s,則當(dāng)前消息的最長(zhǎng)處理時(shí)間為1s。
2、發(fā)起rpc調(diào)用時(shí),再次計(jì)算當(dāng)前消息最長(zhǎng)處理時(shí)間和單個(gè)超時(shí)時(shí)間的最小值,比如:上圖的B->C設(shè)置的單個(gè)超時(shí)時(shí)間為5s,則實(shí)際上B調(diào)用C的真實(shí)超時(shí)仍然是1s,其實(shí)只要超時(shí)時(shí)間大于當(dāng)前最長(zhǎng)處理時(shí)間都是無(wú)效的,都會(huì)取最小值。再比如B->C單個(gè)超時(shí)時(shí)間為500ms,這種情況B調(diào)用C的真實(shí)超時(shí)即為500ms,此時(shí)500ms這個(gè)值也會(huì)通過(guò)協(xié)議字段傳給C,在服務(wù)端C的視角來(lái)看就是他的鏈路超時(shí)時(shí)間。鏈路超時(shí)時(shí)間會(huì)在整個(gè)rpc調(diào)用鏈上一直傳遞下去,并逐漸減少,直至為0,這樣避免出現(xiàn)死循環(huán)調(diào)用的問(wèn)題。
3、因?yàn)槊恳淮蝦pc調(diào)用都會(huì)實(shí)際消耗一部分時(shí)間,所以當(dāng)前消息最長(zhǎng)處理時(shí)間需要實(shí)時(shí)計(jì)算剩余時(shí)間,比如上面B調(diào)用C真實(shí)耗時(shí)200ms,此時(shí)最長(zhǎng)處理時(shí)間就只剩下800ms了。此時(shí)發(fā)起第二次rpc調(diào)用時(shí),則需要計(jì)算此時(shí)剩余的消息超時(shí)時(shí)間和單個(gè)調(diào)用時(shí)間的最小值。如上圖的B->D設(shè)置的單個(gè)超時(shí)時(shí)間為1s,則實(shí)際生效的超時(shí)時(shí)間仍然為800ms。鏈路超時(shí)設(shè)置:golang的context.Context根據(jù)協(xié)議里面的timeout字段和框架配置的timeout字段。設(shè)置好當(dāng)前請(qǐng)求的最長(zhǎng)處理時(shí)間,然后交給用戶使用,并在處理函數(shù)結(jié)束時(shí)會(huì)立馬cancel掉當(dāng)前context。所以在創(chuàng)建新的goroutine時(shí),需要重新設(shè)定新的context。
對(duì)沖策略
對(duì)沖策略不是被動(dòng)的等待上一次請(qǐng)求超時(shí)或者失敗,在對(duì)沖延時(shí)時(shí)間內(nèi)(或小于超時(shí)時(shí)間)如果沒(méi)有收到回復(fù)的包就會(huì)再觸發(fā)一個(gè)請(qǐng)求。
與重試策略不同的是同一時(shí)間內(nèi)in-fliaght可能有多個(gè),當(dāng)接收到第一回復(fù)時(shí),其他的回復(fù)會(huì)被忽略。
一、重試策略:
對(duì)失敗的請(qǐng)求,進(jìn)行重新請(qǐng)求。
由圖中可以看出,client一共進(jìn)行了三次請(qǐng)求,前兩次均失敗,并且在重新請(qǐng)求時(shí)都會(huì)隨機(jī)避段時(shí)間,防止請(qǐng)求毛刺,第三次請(qǐng)求成功,返回給應(yīng)用層。對(duì)于每次嘗試,我們都會(huì)盡可能地將請(qǐng)求發(fā)往不同的節(jié)點(diǎn)。
通常重試策略有三種配置:
1、失敗重新請(qǐng)求的最大次數(shù),達(dá)到最大次數(shù)仍然失敗,不再進(jìn)行重試;
2、退避時(shí)間:退避時(shí)間取的是 random(0, delay);
3、可重試錯(cuò)誤碼:設(shè)置可錯(cuò)誤碼,對(duì)于不可重試的,立即停止重試并將錯(cuò)誤返回應(yīng)用層。
二、對(duì)沖策略
上圖中client一共進(jìn)行了4次,橙、藍(lán)、綠、紫
橙色是第一次嘗試。在由 client 發(fā)起后,server2 很快便收到了。但是 server2 的因?yàn)榫W(wǎng)絡(luò)等問(wèn)題,直到綠色請(qǐng)求成功,并返回給應(yīng)用層后,它的正確回包才姍姍來(lái)遲。盡管它成功了,但我們必須丟棄它,因?yàn)槲覀円呀?jīng)將另一個(gè)成功的回包返回給應(yīng)用層了。
藍(lán)色是第二次嘗試。因?yàn)槌壬?qǐng)求在對(duì)沖時(shí)延(hedging delay)后還沒(méi)有回包,因此我們發(fā)起了一次新的嘗試。這次嘗試選擇了 server1(我們會(huì)盡可能地為每次嘗試選擇不同的節(jié)點(diǎn))。藍(lán)色嘗試的回包比較快,在對(duì)沖時(shí)延之前便返回了。但是卻失敗了。我們立刻發(fā)起了新一次嘗試。
綠色是第三次嘗試。盡管它的回包可能有點(diǎn)慢(超過(guò)了對(duì)沖時(shí)延,因此又觸發(fā)了一次新的嘗試),但是它成功了!一旦我們收到第一個(gè)成功的回包,便立刻將它返回給了應(yīng)用層。
紫色是第四次嘗試。剛發(fā)起后,我們便收到了綠色成功的回包。對(duì)紫色來(lái)說(shuō),它可能處于很多狀態(tài):請(qǐng)求還在 client gRPC 內(nèi),這時(shí),我們有機(jī)會(huì)取消它;請(qǐng)求已經(jīng)進(jìn)入了 client 的內(nèi)核或者已經(jīng)由網(wǎng)卡發(fā)出,無(wú)論如何,我們已經(jīng)沒(méi)有機(jī)會(huì)取消它了。紫色請(qǐng)求上的 ✘ 表示我們會(huì)盡可能地取消紫色請(qǐng)求。注意,即使紫色請(qǐng)求最終成功地到達(dá)了 server2,它的回包也會(huì)像橙色一樣被丟棄。
由以上可知對(duì)沖策略更像是添加了等待時(shí)間的重試,但是他沒(méi)有退避機(jī)制,一旦收到錯(cuò)誤的包,立刻發(fā)起重試。這種對(duì)于需要解決長(zhǎng)尾問(wèn)題時(shí)推薦使用,一般情況建議使用重試策略。
對(duì)沖策略一般有三種配置
1、對(duì)沖延時(shí):在對(duì)對(duì)沖時(shí)延內(nèi)沒(méi)有收到回包時(shí)便會(huì)立刻發(fā)起新的嘗試;
2、最大請(qǐng)求次數(shù):一旦耗盡,便等待并返回最后一個(gè)回包,無(wú)論它是否成功或失??;
3、非致命錯(cuò)誤:返回致命錯(cuò)誤會(huì)立刻中止對(duì)沖,等待并返回最后一個(gè)回包,無(wú)論它是否成功或失敗。返回非致命錯(cuò)誤會(huì)立刻觸發(fā)一次新的嘗試(對(duì)沖時(shí)延計(jì)時(shí)器會(huì)被重置)。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
利用golang進(jìn)行OpenCV學(xué)習(xí)和開(kāi)發(fā)的步驟
目前,OpenCV逐步成為一個(gè)通用的基礎(chǔ)研究和產(chǎn)品開(kāi)發(fā)平臺(tái),下面這篇文章主要給大家介紹了關(guān)于利用golang進(jìn)行OpenCV學(xué)習(xí)和開(kāi)發(fā)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2018-09-09Go連接數(shù)據(jù)庫(kù)操作基礎(chǔ)講解
這篇文章主要為大家介紹了Go連接數(shù)據(jù)庫(kù)操作基礎(chǔ)講解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12Go語(yǔ)言metrics應(yīng)用監(jiān)控指標(biāo)基本使用說(shuō)明
這篇文章主要為大家介紹了Go語(yǔ)言metrics應(yīng)用監(jiān)控指標(biāo)的基本使用說(shuō)明,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-02-02Go?1.21新內(nèi)置函數(shù)min、max和clear的用法詳解
Go?1.21?版本已經(jīng)正式發(fā)布,它帶來(lái)了許多新特性和改進(jìn),其中引入了的三個(gè)新內(nèi)置函數(shù):max、min?和?clear,接下來(lái)我們就來(lái)看看這些函數(shù)的用途和特點(diǎn)吧2023-08-08一篇文章說(shuō)清楚?go?get?使用私有庫(kù)的方法
這篇文章主要介紹了go?get?如何使用私有庫(kù),本文會(huì)明確指出Git?、golang的配置項(xiàng),附送TortoiseGit?+?Git混合配置,需要的朋友可以參考下2022-09-09