什麼是重試
如果服務出現了錯誤,主要是網絡,服務器出現了短暫異常的時候,該怎麼辦?
我們都會人工或者自動的重新連接服務試試,看服務是否恢復可用了。
這種重新進行連接服務的一種方式就是重試。如果是在微服務裏,應該屬於微服務治理的範疇。
重試是處理網絡服務出現暫時不可用的一種方法。
怎麼進行重試
第一:你要能知道網絡出現了錯誤。
怎麼才能知道網絡出現了錯誤呢?
一種是你主動探測網絡是否可用,比如說健康檢查;一種是根據定義的錯誤碼來進行重試。比如http的一些錯誤碼。
第二:根據上面的探測或者錯誤碼決定是否重試。
因爲不是所有的錯誤都要進行重試,我們要根據具體情況來做決定是否重試。
第三:重試的策略
就是怎麼進行重試。
在gRPC的這份設計中,主要有2中重試策略:
-
Retry Policy,出錯時立即重試
-
Hedging Policy,定時發送併發的多個請求,根據請求的響應情況決定是否發送下一個同樣的請求,還是返回(好像該策略目前未實現)
-
在Retry和Heading基礎上的限流
客戶端的重試策略和限流策略都是用一個配置文件配置 - service config,這個配置文件用proto定義了一些字段和格式,文件在 grpc/service_config/service_config.proto 中,它最終會解析爲一個json格式,proto->json 規則文檔。
一些Service Config文檔例子:doc/service_config
例子
參考官方的例子把我們前面的hello word程序用retry策略改寫下。
客戶端
主要是要在 Dial()
建立連接的這個函數里加一個參數 grpc.WithDefaultServiceConfig()
,這個就是配置重試策略的參數。
conn, err := grpc.Dial(Address, grpc.WithInsecure(),grpc.WithDefaultServiceConfig(retryPolicy))
裏面的參數 retryPolicy
是一個json的字符串,這個就是service config:
retryPolicy = `{
"methodConfig":[{
"name":[{"service": "grpc-tutorial.05retry.hello.hello"}],
"waitForReady": true,
"retryPolicy": {
"MaxAttempts": 4,
"InitialBakckoff": ".01s",
"MaxBackoff": ".01s",
"BackoffMultiplier": 1.0,
"RetryableStatusCodes": ["UNAVAILABLE"]
}
}]}`
- MaxAttempts:
最多請求次數。這裏設置爲4,一次原始請求,三次重試請求。
MaxAttempts必須是大於1的整數,對於大於5的值會被視爲5。
- InitialBakckoff, BackoffMultiplier, MaxBackoff
這3個參數要結合看, 意思是在進行下一次重試請求前,會計算需要等待的時間,計算公式爲:- 第一次重試間隔是
random(0, InitialBakckoff)
- 第 n 次的重試間隔爲
random(0, min( InitialBakckoff*BackoffMultiplier*(n-1) , MaxBackoff ))
- 第一次重試間隔是
InitialBakckoff 和 MaxBackoff 必須指定,並且必須具有大於0。
BackoffMultiplier 必須指定,並且大於零。
- RetryableStatusCodes
會根據這個RetryableStatusCodes
來判斷是否進行重試。這裏設置爲UNAVAILABLE
,會根據這個狀態來進行重試。
retryableStatusCodes 必須制定爲狀態碼的數據,不能爲空,並且沒有狀態碼必須是有效的 gPRC 狀態碼,可以是整數形式,並且不區分大小寫 ([14], [“UNAVAILABLE”], [“unavailable”)
服務端
在服務端我要製造重試的情況出來,主要是 failRequest() 這個函數,改寫一下程序:
type failServer struct {
pb.UnimplementedHelloServer
mu sync.Mutex
reqNum uint
reqMax uint
}
func (f *failServer) failRequest() error {
f.mu.Lock()
defer f.mu.Unlock()
f.reqNum++
if (f.reqMax > 0) && (f.reqNum % f.reqMax == 0) {
return nil
}
return status.Errorf(codes.Unavailable, "failRequest: failing it")
}
然後在main函數初始化一下這個failServer struct:
failService := &failServer{
reqNum: 0,
reqMax: 4,
}
完整的例子在:這裏github
運行看看:
先運行服務端:go run server/main.go
在運行客戶端:GRPC_GO_RETRY=on go run client/main.go
注意:
運行客戶端一定要在環境變量里加上 GRPC_GO_RETRY=on
可是報錯了:
sayhello err: rpc error: code = Unavailable desc = failRequest: failing it
exit status 1
而且服務端也只打印了一條信息:
request failed num: 1