client: determenistic retry backoff

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
This commit is contained in:
Василий Толстов 2022-03-27 00:16:22 +03:00
parent 35ab6ae84e
commit 680ac11ef9
4 changed files with 36 additions and 7 deletions

View File

@ -2,6 +2,7 @@ package client
import ( import (
"context" "context"
"math"
"time" "time"
"go.unistack.org/micro/v3/util/backoff" "go.unistack.org/micro/v3/util/backoff"
@ -10,6 +11,20 @@ import (
// BackoffFunc is the backoff call func // BackoffFunc is the backoff call func
type BackoffFunc func(ctx context.Context, req Request, attempts int) (time.Duration, error) type BackoffFunc func(ctx context.Context, req Request, attempts int) (time.Duration, error)
func exponentialBackoff(ctx context.Context, req Request, attempts int) (time.Duration, error) { // BackoffExp using exponential backoff func
func BackoffExp(_ context.Context, _ Request, attempts int) (time.Duration, error) {
return backoff.Do(attempts), nil return backoff.Do(attempts), nil
} }
// BackoffInterval specifies randomization interval for backoff func
func BackoffInterval(min time.Duration, max time.Duration) BackoffFunc {
return func(_ context.Context, _ Request, attempts int) (time.Duration, error) {
td := time.Duration(time.Duration(math.Pow(float64(attempts), math.E)) * time.Millisecond * 100)
if td < min {
return min, nil
} else if td > max {
return max, nil
}
return td, nil
}
}

View File

@ -22,7 +22,7 @@ func TestBackoff(t *testing.T) {
} }
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
d, err := exponentialBackoff(context.TODO(), r, i) d, err := BackoffExp(context.TODO(), r, i)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -14,8 +14,8 @@ var (
DefaultClient Client = NewClient() DefaultClient Client = NewClient()
// DefaultContentType is the default content-type if not specified // DefaultContentType is the default content-type if not specified
DefaultContentType = "application/json" DefaultContentType = "application/json"
// DefaultBackoff is the default backoff function for retries // DefaultBackoff is the default backoff function for retries (minimum 10 millisecond and maximum 5 second)
DefaultBackoff = exponentialBackoff DefaultBackoff = BackoffInterval(10*time.Millisecond, 5*time.Second)
// DefaultRetry is the default check-for-retry function for retries // DefaultRetry is the default check-for-retry function for retries
DefaultRetry = RetryNever DefaultRetry = RetryNever
// DefaultRetries is the default number of times a request is tried // DefaultRetries is the default number of times a request is tried

View File

@ -19,18 +19,32 @@ func RetryNever(ctx context.Context, req Request, retryCount int, err error) (bo
return false, nil return false, nil
} }
// RetryOnError retries a request on a 500 or timeout error // RetryOnError retries a request on a 500 or 408 (timeout) error
func RetryOnError(_ context.Context, _ Request, _ int, err error) (bool, error) { func RetryOnError(_ context.Context, _ Request, _ int, err error) (bool, error) {
if err == nil { if err == nil {
return false, nil return false, nil
} }
me := errors.FromError(err) me := errors.FromError(err)
switch me.Code { switch me.Code {
// retry on timeout or internal server error // retry on timeout or internal server error
case 408, 500: case 408, 500:
return true, nil return true, nil
} }
return false, nil return false, nil
} }
// RetryOnErrors retries a request on specified error codes
func RetryOnErrors(codes ...int32) RetryFunc {
return func(_ context.Context, _ Request, _ int, err error) (bool, error) {
if err == nil {
return false, nil
}
me := errors.FromError(err)
for _, code := range codes {
if me.Code == code {
return true, nil
}
}
return false, nil
}
}