From 680ac11ef97e2ed04b2f6b5e387086466ae81676 Mon Sep 17 00:00:00 2001 From: Vasiliy Tolstov Date: Sun, 27 Mar 2022 00:16:22 +0300 Subject: [PATCH 1/2] client: determenistic retry backoff Signed-off-by: Vasiliy Tolstov --- client/backoff.go | 17 ++++++++++++++++- client/backoff_test.go | 2 +- client/client.go | 4 ++-- client/retry.go | 20 +++++++++++++++++--- 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/client/backoff.go b/client/backoff.go index 0aed7c60..d548b724 100644 --- a/client/backoff.go +++ b/client/backoff.go @@ -2,6 +2,7 @@ package client import ( "context" + "math" "time" "go.unistack.org/micro/v3/util/backoff" @@ -10,6 +11,20 @@ import ( // BackoffFunc is the backoff call func 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 } + +// 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 + } +} diff --git a/client/backoff_test.go b/client/backoff_test.go index 5b9b60b8..2ab854f7 100644 --- a/client/backoff_test.go +++ b/client/backoff_test.go @@ -22,7 +22,7 @@ func TestBackoff(t *testing.T) { } for i := 0; i < 5; i++ { - d, err := exponentialBackoff(context.TODO(), r, i) + d, err := BackoffExp(context.TODO(), r, i) if err != nil { t.Fatal(err) } diff --git a/client/client.go b/client/client.go index 63cb4f85..71bedfd9 100644 --- a/client/client.go +++ b/client/client.go @@ -14,8 +14,8 @@ var ( DefaultClient Client = NewClient() // DefaultContentType is the default content-type if not specified DefaultContentType = "application/json" - // DefaultBackoff is the default backoff function for retries - DefaultBackoff = exponentialBackoff + // DefaultBackoff is the default backoff function for retries (minimum 10 millisecond and maximum 5 second) + DefaultBackoff = BackoffInterval(10*time.Millisecond, 5*time.Second) // DefaultRetry is the default check-for-retry function for retries DefaultRetry = RetryNever // DefaultRetries is the default number of times a request is tried diff --git a/client/retry.go b/client/retry.go index 40272fd2..a218b3fe 100644 --- a/client/retry.go +++ b/client/retry.go @@ -19,18 +19,32 @@ func RetryNever(ctx context.Context, req Request, retryCount int, err error) (bo 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) { if err == nil { return false, nil } - me := errors.FromError(err) switch me.Code { // retry on timeout or internal server error case 408, 500: return true, 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 + } +} -- 2.45.2 From 03410c4ab141ce3ffac63b120ab3538fdb258308 Mon Sep 17 00:00:00 2001 From: Vasiliy Tolstov Date: Sun, 27 Mar 2022 01:37:21 +0300 Subject: [PATCH 2/2] small improve Signed-off-by: Vasiliy Tolstov --- router/context.go | 34 ++++++++++++++++++++++++++++++++++ util/jitter/random.go | 5 +++++ 2 files changed, 39 insertions(+) create mode 100644 router/context.go diff --git a/router/context.go b/router/context.go new file mode 100644 index 00000000..2f156cfc --- /dev/null +++ b/router/context.go @@ -0,0 +1,34 @@ +package router + +import ( + "context" +) + +type routerKey struct{} + +// FromContext get router from context +func FromContext(ctx context.Context) (Router, bool) { + if ctx == nil { + return nil, false + } + c, ok := ctx.Value(routerKey{}).(Router) + return c, ok +} + +// NewContext put router in context +func NewContext(ctx context.Context, c Router) context.Context { + if ctx == nil { + ctx = context.Background() + } + return context.WithValue(ctx, routerKey{}, c) +} + +// SetOption returns a function to setup a context with given value +func SetOption(k, v interface{}) Option { + return func(o *Options) { + if o.Context == nil { + o.Context = context.Background() + } + o.Context = context.WithValue(o.Context, k, v) + } +} diff --git a/util/jitter/random.go b/util/jitter/random.go index c3caa5d3..eb03b29f 100644 --- a/util/jitter/random.go +++ b/util/jitter/random.go @@ -13,3 +13,8 @@ func Random(d time.Duration) time.Duration { v := rng.Float64() * float64(d.Nanoseconds()) return time.Duration(v) } + +func RandomInterval(min, max time.Duration) time.Duration { + var rng rand.Rand + return time.Duration(rng.Int63n(max.Nanoseconds()-min.Nanoseconds())+min.Nanoseconds()) * time.Nanosecond +} -- 2.45.2