「GoCN酷Go推薦」重試工具 — retry-go
簡(jiǎn)介
在微服務(wù)架構(gòu)中,通常會(huì)有很多的小服務(wù),小服務(wù)之間存在大量 RPC 調(diào)用,但時(shí)常因?yàn)榫W(wǎng)絡(luò)抖動(dòng)等原因,造成請(qǐng)求失敗,這時(shí)候使用重試機(jī)制可以提高請(qǐng)求的最終成功率,減少故障影響,讓系統(tǒng)運(yùn)行更穩(wěn)定。retry-go 是一個(gè)功能比較完善的 golang 重試庫(kù)。
如何使用
retry-go的使用非常簡(jiǎn)單,直接使用 Do方法即可。如下是一個(gè)發(fā)起 HTTP Get 請(qǐng)求的重試示例 :
url?:=?"https://gocn.vip"
var?body?[]byte
err?:=?retry.Do(
?func()?error?{
??resp,?err?:=?http.Get(url)
??if?err?!=?nil?{
???return?err
??}
??defer?resp.Body.Close()
??body,?err?=?ioutil.ReadAll(resp.Body)
??if?err?!=?nil?{
???return?err
??}
??return?nil
?},
)
fmt.Println(body)
調(diào)用時(shí),有一些可選的配置項(xiàng):
attempts 最大重試次數(shù) delay 重試延遲時(shí)間 maxDelay 最大重試延遲時(shí)間,選擇指數(shù)退避策略時(shí),該配置會(huì)限制等待時(shí)間上限 maxJitter 隨機(jī)退避策略的最大等待時(shí)間 onRetry 每次重試時(shí)進(jìn)行的一次回調(diào) retryIf 重試時(shí)的一個(gè)條件判斷 delayType 退避策略類型 lastErrorOnly 是否只返回上次重試的錯(cuò)誤
BackOff 退避策略
對(duì)于一些暫時(shí)性的錯(cuò)誤,如網(wǎng)絡(luò)抖動(dòng)等,立即重試可能還是會(huì)失敗,通常等待一小會(huì)兒再重試的話成功率會(huì)較高,并且這種策略也可以打散上游重試的時(shí)間,避免同時(shí)重試而導(dǎo)致的瞬間流量高峰。決定等待多久之后再重試的方法叫做退避策略。retry-go 實(shí)現(xiàn)了以下幾個(gè)退避策略:
func BackOffDelay
func?BackOffDelay(n?uint,?_?error,?config?*Config)?time.Duration
BackOffDelay 提供一個(gè)指數(shù)避退策略,連續(xù)重試時(shí),每次等待時(shí)間都是前一次的 2 倍。
func FixedDelay
func?FixedDelay(_?uint,?_?error,?config?*Config)?time.Duration
FixedDelay 在每次重試時(shí),等待一個(gè)固定延遲時(shí)間。
func RandomDelay
func?RandomDelay(_?uint,?_?error,?config?*Config)?time.Duration
RandomDelay 在 0 - config.maxJitter 內(nèi)隨機(jī)等待一個(gè)時(shí)間后重試。
func CombineDelay
func?CombineDelay(delays?...DelayTypeFunc)?DelayTypeFunc
CombineDelay ?提供結(jié)合多種策略實(shí)現(xiàn)一個(gè)新策略的能力。
retry-go默認(rèn)的退避策略為 ?BackOffDelay和RandomDelay結(jié)合的方式,即在指數(shù)遞增的同時(shí),加一個(gè)隨機(jī)時(shí)間。
自定義的延時(shí)策略
下面是一個(gè)官方給出的例子,當(dāng)請(qǐng)求的響應(yīng)有Retry-After頭時(shí),使用該值去進(jìn)行等待,其他情況按照BackOffDelay策略進(jìn)行延時(shí)等待。
var?_?error?=?(*RetriableError)(nil)
func?test2(){
?var?body?[]byte
?err?:=?retry.Do(
??func()?error?{
???resp,?err?:=?http.Get("URL")
???if?err?==?nil?{
????defer?func()?{
?????if?err?:=?resp.Body.Close();?err?!=?nil?{
??????panic(err)
?????}
????}()
????body,?err?=?ioutil.ReadAll(resp.Body)
????if?resp.StatusCode?!=?200?{
?????err?=?fmt.Errorf("HTTP?%d:?%s",?resp.StatusCode,?string(body))
?????if?resp.StatusCode?==?http.StatusTooManyRequests?{
??????//?check?Retry-After?header?if?it?contains?seconds?to?wait?for?the?next?retry
??????if?retryAfter,?e?:=?strconv.ParseInt(resp.Header.Get("Retry-After"),?10,?32);?e?==?nil?{
???????//?the?server?returns?0?to?inform?that?the?operation?cannot?be?retried
???????if?retryAfter?<=?0?{
????????return?retry.Unrecoverable(err)
???????}
???????return?&RetriableError{
????????Err:????????err,
????????RetryAfter:?time.Duration(retryAfter)?*?time.Second,
???????}
??????}
??????//?A?real?implementation?should?also?try?to?http.Parse?the?retryAfter?response?header
??????//?to?conform?with?HTTP?specification.?Herein?we?know?here?that?we?return?only?seconds.
?????}
????}
???}
???return?err
??},
??retry.DelayType(func(n?uint,?err?error,?config?*retry.Config)?time.Duration?{
???fmt.Println("Server?fails?with:?"?+?err.Error())
???if?retriable,?ok?:=?err.(*RetriableError);?ok?{
????fmt.Printf("Client?follows?server?recommendation?to?retry?after?%v\n",?retriable.RetryAfter)
????return?retriable.RetryAfter
???}
???//?apply?a?default?exponential?back?off?strategy
???return?retry.BackOffDelay(n,?err,?config)
??}),
?)
?fmt.Println("Server?responds?with:?"?+?string(body))
}
總結(jié)
重試可以提升服務(wù)調(diào)用的成功率,但重試時(shí)也要警惕由此帶來(lái)的放大故障的風(fēng)險(xiǎn)。選擇合適的退避策略,控制放大效應(yīng),才能優(yōu)雅的提升服務(wù)的穩(wěn)定性。
Reference
如何優(yōu)雅地重試-InfoQ
[譯] 重試、超時(shí)和退避 | nettee 的 blog
《酷Go推薦》招募:
各位Gopher同學(xué),最近我們社區(qū)打算推出一個(gè)類似GoCN每日新聞的新欄目《酷Go推薦》,主要是每周推薦一個(gè)庫(kù)或者好的項(xiàng)目,然后寫一點(diǎn)這個(gè)庫(kù)使用方法或者優(yōu)點(diǎn)之類的,這樣可以真正的幫助到大家能夠?qū)W習(xí)到
新的庫(kù),并且知道怎么用。
大概規(guī)則和每日新聞?lì)愃疲绻麍?bào)名人多的話每個(gè)人一個(gè)月輪到一次,歡迎大家報(bào)名!戳「閱讀原文」,即可報(bào)名
掃碼也可以加入 GoCN 的大家族喲~
