loadingcache 有重试机制吗_重试机制的实现
服務在請求資源,如果遇到網絡異常等情況,導致請求失敗,這時需要有個重試機制來繼續請求。 常見的做法是重試3次,并隨機 sleep 幾秒。 業務開發的腳手架,HTTP Client 基本會封裝好 retry 方法,請求失敗時根據配置自動重試。下面以一個常見的 HTTP Client 為例, 看下它是如何實現請求重試。 最后整理其他一些重試機制的實現。
go-resty 重試機制的實現
先看下 go-resty 在發送 HTTP 請求時, 請求重試的實現:
// Execute method performs the HTTP request with given HTTP method and URL// for current `Request`.// resp, err := client.R().Execute(resty.GET, "http://httpbin.org/get")func (r *Request) Execute(method, url string) (*Response, error) {
var addrs []*net.SRV
var resp *Response
var err error
if r.isMultiPart && !(method == MethodPost || method == MethodPut || method == MethodPatch) {
return nil, fmt.Errorf("multipart content is not allowed in HTTP verb [%v]", method)
}
if r.SRV != nil {
_, addrs, err = net.LookupSRV(r.SRV.Service, "tcp", r.SRV.Domain)
if err != nil {
return nil, err
}
}
r.Method = method
r.URL = r.selectAddr(addrs, url, 0)
if r.client.RetryCount == 0 {
resp, err = r.client.execute(r)
return resp, unwrapNoRetryErr(err)
}
attempt := 0
err = Backoff(
func() (*Response, error) {
attempt++
r.URL = r.selectAddr(addrs, url, attempt)
resp, err = r.client.execute(r)
if err != nil {
r.client.log.Errorf("%v, Attempt %v", err, attempt)
}
return resp, err
},
Retries(r.client.RetryCount),
WaitTime(r.client.RetryWaitTime),
MaxWaitTime(r.client.RetryMaxWaitTime),
RetryConditions(r.client.RetryConditions),
)
return resp, unwrapNoRetryErr(err)
}
重試流程
梳理 Execute(method, url) 在請求時的重試流程:如果沒有設置重試次數,執行 r.client.execute(r) :直接請求 Request , 返回 Response 和 error。
如果 r.client.RetryCount 不等于0 ,執行 Backoff() 函數
Backoff() 方法接收一個處理函數參數,根據重試策略, 進行 attempt 次網絡請求, 同時接收 Retries()、WaitTime()等函數參數
Backoff函數
重點看下 Backoff() 函數做了什么動作。
Backoff()代碼如下:
// Backoff retries with increasing timeout duration up until X amount of retries// (Default is 3 attempts, Override with option Retries(n))func Backoff(operation func() (*Response, error), options ...Option) error {
// Defaults opts := Options{
maxRetries: defaultMaxRetries,
waitTime: defaultWaitTime,
maxWaitTime: defaultMaxWaitTime,
retryConditions: []RetryConditionFunc{},
}
for _, o := range options {
o(&opts)
}
var (
resp *Response
err error
)
for attempt := 0; attempt <= opts.maxRetries; attempt++ {
resp, err = operation()
ctx := context.Background()
if resp != nil && resp.Request.ctx != nil {
ctx = resp.Request.ctx
}
if ctx.Err() != nil {
return err
}
err1 := unwrapNoRetryErr(err) // raw error, it used for return users callback. needsRetry := err != nil && err == err1 // retry on a few operation errors by default
for _, condition := range opts.retryConditions {
needsRetry = condition(resp, err1)
if needsRetry {
break
}
}
if !needsRetry {
return err
}
waitTime, err2 := sleepDuration(resp, opts.waitTime, opts.maxWaitTime, attempt)
if err2 != nil {
if err == nil {
err = err2
}
return err
}
select {
case
case
return ctx.Err()
}
}
return err
}
梳理 Backoff() 函數的流程:Backoff() 接收 處理函數 和 可選的 Option 函數(retry optione) 作為參數
默認策略3次重試, 通過 步驟一 預設的 Options, 自定義重試策略
設置請求的 repsonse 和 error 變量
開始進行 opts.maxRetries 次 HTTP 請求:
執行處理函數 (發起 HTTP 請求)
如果返回結果不為空并且 context 不為空,保持 repsonse 的請求上下文。 如果上下文出錯, 退出 Backoff() 流程
執行 retryConditions(), 設置檢查重試的條件。
根據 needsRetry 判斷是否退出流程
通過 sleepDuration()計算 Duration(根據此次請求resp, 等待時間配置,最大超時時間和重試次數算出 sleepDuration。 時間算法相對復雜, 具體參考: Exponential Backoff And Jitter)
等待 waitTime 進行下個重試。 如果請求完成退出流程。
一個簡單的 Demo
看具體 HTTP Client (有做過簡單封裝)的請求:
func getInfo() {
request := client.DefaultClient().
NewRestyRequest(ctx, "", client.RequestOptions{
MaxTries: 3,
RetryWaitTime: 500 * time.Millisecond,
RetryConditionFunc: func(response *resty.Response) (b bool, err error) {
if !response.IsSuccess() {
return true, nil
}
return
},
}).SetAuthToken(args.Token)
resp, err := request.Get(url)
if err != nil {
logger.Error(ctx, err)
return
}
body := resp.Body()
if resp.StatusCode() != 200 {
logger.Error(ctx, fmt.Sprintf("Request keycloak access token failed, messages:%s, body:%s","message", resp.Status(),string(body))),
)
return
}
...
}
根據以上梳理的 go-resty 的請求流程, 因為 RetryCount 大于0,所以會進行重試機制,重試次數為3。然后 request.Get(url) 進入到 Backoff() 流程,此時重試的邊界條件是: !response.IsSuccess(), 直到請求成功。
一些其他重試機制的實現
可以看出其實 go-resty 的 重試策略不是很簡單, 這是一個完善,可定制化, 充分考慮 HTTP 請求場景下的一個機制, 它的業務屬性相對比較重。
再來看看兩個常見的 Retry 實現:
實現一
// retry retries ephemeral errors from f up to an arbitrary timeoutfunc retry(f func() (err error, mayRetry bool)) error {
var (
bestErr error
lowestErrno syscall.Errno
start time.Time
nextSleep time.Duration = 1 * time.Millisecond
)
for {
err, mayRetry := f()
if err == nil || !mayRetry {
return err
}
if errno, ok := err.(syscall.Errno); ok && (lowestErrno == 0 || errno < lowestErrno) {
bestErr = err
lowestErrno = errno
} else if bestErr == nil {
bestErr = err
}
if start.IsZero() {
start = time.Now()
} else if d := time.Since(start) + nextSleep; d >= arbitraryTimeout {
break
}
time.Sleep(nextSleep)
nextSleep += time.Duration(rand.Int63n(int64(nextSleep)))
}
return bestErr
}
每次重試等待隨機延長的時間, 直到 f() 執行完成 或不再重試。
實現二
func Retry(attempts int, sleep time.Duration, f func() error) (err error) {
for i := 0; ; i++ {
err = f()
if err == nil {
return
}
if i >= (attempts - 1) {
break
}
time.Sleep(sleep)
}
return fmt.Errorf("after %d attempts, last error: %v", attempts, err)
}
對函數重試 attempts 次,每次等待 sleep 時間, 直到 f() 執行完成。
總結
以上是生活随笔為你收集整理的loadingcache 有重试机制吗_重试机制的实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: epoll监听文件_怎么理解把标准输入以
- 下一篇: docker安装clickhouse_D