mirror of
https://gitee.com/johng/gf
synced 2026-07-08 14:39:50 +08:00
新增go modules支持,自行管理第三方包依赖,方便开发者使用
This commit is contained in:
26
third/github.com/eapache/go-resiliency/retrier/README.md
Normal file
26
third/github.com/eapache/go-resiliency/retrier/README.md
Normal file
@ -0,0 +1,26 @@
|
||||
retrier
|
||||
=======
|
||||
|
||||
[](https://travis-ci.org/eapache/go-resiliency)
|
||||
[](https://godoc.org/github.com/eapache/go-resiliency/retrier)
|
||||
[](https://eapache.github.io/conduct.html)
|
||||
|
||||
The retriable resiliency pattern for golang.
|
||||
|
||||
Creating a retrier takes two parameters:
|
||||
- the times to back-off between retries (and implicitly the number of times to
|
||||
retry)
|
||||
- the classifier that determines which errors to retry
|
||||
|
||||
```go
|
||||
r := retrier.New(retrier.ConstantBackoff(3, 100*time.Millisecond), nil)
|
||||
|
||||
err := r.Run(func() error {
|
||||
// do some work
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
// handle the case where the work failed three times
|
||||
}
|
||||
```
|
||||
24
third/github.com/eapache/go-resiliency/retrier/backoffs.go
Normal file
24
third/github.com/eapache/go-resiliency/retrier/backoffs.go
Normal file
@ -0,0 +1,24 @@
|
||||
package retrier
|
||||
|
||||
import "time"
|
||||
|
||||
// ConstantBackoff generates a simple back-off strategy of retrying 'n' times, and waiting 'amount' time after each one.
|
||||
func ConstantBackoff(n int, amount time.Duration) []time.Duration {
|
||||
ret := make([]time.Duration, n)
|
||||
for i := range ret {
|
||||
ret[i] = amount
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// ExponentialBackoff generates a simple back-off strategy of retrying 'n' times, and doubling the amount of
|
||||
// time waited after each one.
|
||||
func ExponentialBackoff(n int, initialAmount time.Duration) []time.Duration {
|
||||
ret := make([]time.Duration, n)
|
||||
next := initialAmount
|
||||
for i := range ret {
|
||||
ret[i] = next
|
||||
next *= 2
|
||||
}
|
||||
return ret
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
package retrier
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestConstantBackoff(t *testing.T) {
|
||||
b := ConstantBackoff(1, 10*time.Millisecond)
|
||||
if len(b) != 1 {
|
||||
t.Error("incorrect length")
|
||||
}
|
||||
for i := range b {
|
||||
if b[i] != 10*time.Millisecond {
|
||||
t.Error("incorrect value at", i)
|
||||
}
|
||||
}
|
||||
|
||||
b = ConstantBackoff(10, 250*time.Hour)
|
||||
if len(b) != 10 {
|
||||
t.Error("incorrect length")
|
||||
}
|
||||
for i := range b {
|
||||
if b[i] != 250*time.Hour {
|
||||
t.Error("incorrect value at", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExponentialBackoff(t *testing.T) {
|
||||
b := ExponentialBackoff(1, 10*time.Millisecond)
|
||||
if len(b) != 1 {
|
||||
t.Error("incorrect length")
|
||||
}
|
||||
if b[0] != 10*time.Millisecond {
|
||||
t.Error("incorrect value")
|
||||
}
|
||||
|
||||
b = ExponentialBackoff(4, 1*time.Minute)
|
||||
if len(b) != 4 {
|
||||
t.Error("incorrect length")
|
||||
}
|
||||
if b[0] != 1*time.Minute {
|
||||
t.Error("incorrect value")
|
||||
}
|
||||
if b[1] != 2*time.Minute {
|
||||
t.Error("incorrect value")
|
||||
}
|
||||
if b[2] != 4*time.Minute {
|
||||
t.Error("incorrect value")
|
||||
}
|
||||
if b[3] != 8*time.Minute {
|
||||
t.Error("incorrect value")
|
||||
}
|
||||
}
|
||||
66
third/github.com/eapache/go-resiliency/retrier/classifier.go
Normal file
66
third/github.com/eapache/go-resiliency/retrier/classifier.go
Normal file
@ -0,0 +1,66 @@
|
||||
package retrier
|
||||
|
||||
// Action is the type returned by a Classifier to indicate how the Retrier should proceed.
|
||||
type Action int
|
||||
|
||||
const (
|
||||
Succeed Action = iota // Succeed indicates the Retrier should treat this value as a success.
|
||||
Fail // Fail indicates the Retrier should treat this value as a hard failure and not retry.
|
||||
Retry // Retry indicates the Retrier should treat this value as a soft failure and retry.
|
||||
)
|
||||
|
||||
// Classifier is the interface implemented by anything that can classify Errors for a Retrier.
|
||||
type Classifier interface {
|
||||
Classify(error) Action
|
||||
}
|
||||
|
||||
// DefaultClassifier classifies errors in the simplest way possible. If
|
||||
// the error is nil, it returns Succeed, otherwise it returns Retry.
|
||||
type DefaultClassifier struct{}
|
||||
|
||||
// Classify implements the Classifier interface.
|
||||
func (c DefaultClassifier) Classify(err error) Action {
|
||||
if err == nil {
|
||||
return Succeed
|
||||
}
|
||||
|
||||
return Retry
|
||||
}
|
||||
|
||||
// WhitelistClassifier classifies errors based on a whitelist. If the error is nil, it
|
||||
// returns Succeed; if the error is in the whitelist, it returns Retry; otherwise, it returns Fail.
|
||||
type WhitelistClassifier []error
|
||||
|
||||
// Classify implements the Classifier interface.
|
||||
func (list WhitelistClassifier) Classify(err error) Action {
|
||||
if err == nil {
|
||||
return Succeed
|
||||
}
|
||||
|
||||
for _, pass := range list {
|
||||
if err == pass {
|
||||
return Retry
|
||||
}
|
||||
}
|
||||
|
||||
return Fail
|
||||
}
|
||||
|
||||
// BlacklistClassifier classifies errors based on a blacklist. If the error is nil, it
|
||||
// returns Succeed; if the error is in the blacklist, it returns Fail; otherwise, it returns Retry.
|
||||
type BlacklistClassifier []error
|
||||
|
||||
// Classify implements the Classifier interface.
|
||||
func (list BlacklistClassifier) Classify(err error) Action {
|
||||
if err == nil {
|
||||
return Succeed
|
||||
}
|
||||
|
||||
for _, pass := range list {
|
||||
if err == pass {
|
||||
return Fail
|
||||
}
|
||||
}
|
||||
|
||||
return Retry
|
||||
}
|
||||
@ -0,0 +1,66 @@
|
||||
package retrier
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
errFoo = errors.New("FOO")
|
||||
errBar = errors.New("BAR")
|
||||
errBaz = errors.New("BAZ")
|
||||
)
|
||||
|
||||
func TestDefaultClassifier(t *testing.T) {
|
||||
c := DefaultClassifier{}
|
||||
|
||||
if c.Classify(nil) != Succeed {
|
||||
t.Error("default misclassified nil")
|
||||
}
|
||||
|
||||
if c.Classify(errFoo) != Retry {
|
||||
t.Error("default misclassified foo")
|
||||
}
|
||||
if c.Classify(errBar) != Retry {
|
||||
t.Error("default misclassified bar")
|
||||
}
|
||||
if c.Classify(errBaz) != Retry {
|
||||
t.Error("default misclassified baz")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWhitelistClassifier(t *testing.T) {
|
||||
c := WhitelistClassifier{errFoo, errBar}
|
||||
|
||||
if c.Classify(nil) != Succeed {
|
||||
t.Error("whitelist misclassified nil")
|
||||
}
|
||||
|
||||
if c.Classify(errFoo) != Retry {
|
||||
t.Error("whitelist misclassified foo")
|
||||
}
|
||||
if c.Classify(errBar) != Retry {
|
||||
t.Error("whitelist misclassified bar")
|
||||
}
|
||||
if c.Classify(errBaz) != Fail {
|
||||
t.Error("whitelist misclassified baz")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlacklistClassifier(t *testing.T) {
|
||||
c := BlacklistClassifier{errBar}
|
||||
|
||||
if c.Classify(nil) != Succeed {
|
||||
t.Error("blacklist misclassified nil")
|
||||
}
|
||||
|
||||
if c.Classify(errFoo) != Retry {
|
||||
t.Error("blacklist misclassified foo")
|
||||
}
|
||||
if c.Classify(errBar) != Fail {
|
||||
t.Error("blacklist misclassified bar")
|
||||
}
|
||||
if c.Classify(errBaz) != Retry {
|
||||
t.Error("blacklist misclassified baz")
|
||||
}
|
||||
}
|
||||
69
third/github.com/eapache/go-resiliency/retrier/retrier.go
Normal file
69
third/github.com/eapache/go-resiliency/retrier/retrier.go
Normal file
@ -0,0 +1,69 @@
|
||||
// Package retrier implements the "retriable" resiliency pattern for Go.
|
||||
package retrier
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Retrier implements the "retriable" resiliency pattern, abstracting out the process of retrying a failed action
|
||||
// a certain number of times with an optional back-off between each retry.
|
||||
type Retrier struct {
|
||||
backoff []time.Duration
|
||||
class Classifier
|
||||
jitter float64
|
||||
rand *rand.Rand
|
||||
}
|
||||
|
||||
// New constructs a Retrier with the given backoff pattern and classifier. The length of the backoff pattern
|
||||
// indicates how many times an action will be retried, and the value at each index indicates the amount of time
|
||||
// waited before each subsequent retry. The classifier is used to determine which errors should be retried and
|
||||
// which should cause the retrier to fail fast. The DefaultClassifier is used if nil is passed.
|
||||
func New(backoff []time.Duration, class Classifier) *Retrier {
|
||||
if class == nil {
|
||||
class = DefaultClassifier{}
|
||||
}
|
||||
|
||||
return &Retrier{
|
||||
backoff: backoff,
|
||||
class: class,
|
||||
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
}
|
||||
}
|
||||
|
||||
// Run executes the given work function, then classifies its return value based on the classifier used
|
||||
// to construct the Retrier. If the result is Succeed or Fail, the return value of the work function is
|
||||
// returned to the caller. If the result is Retry, then Run sleeps according to the its backoff policy
|
||||
// before retrying. If the total number of retries is exceeded then the return value of the work function
|
||||
// is returned to the caller regardless.
|
||||
func (r *Retrier) Run(work func() error) error {
|
||||
retries := 0
|
||||
for {
|
||||
ret := work()
|
||||
|
||||
switch r.class.Classify(ret) {
|
||||
case Succeed, Fail:
|
||||
return ret
|
||||
case Retry:
|
||||
if retries >= len(r.backoff) {
|
||||
return ret
|
||||
}
|
||||
time.Sleep(r.calcSleep(retries))
|
||||
retries++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Retrier) calcSleep(i int) time.Duration {
|
||||
// take a random float in the range (-r.jitter, +r.jitter) and multiply it by the base amount
|
||||
return r.backoff[i] + time.Duration(((r.rand.Float64()*2)-1)*r.jitter*float64(r.backoff[i]))
|
||||
}
|
||||
|
||||
// SetJitter sets the amount of jitter on each back-off to a factor between 0.0 and 1.0 (values outside this range
|
||||
// are silently ignored). When a retry occurs, the back-off is adjusted by a random amount up to this value.
|
||||
func (r *Retrier) SetJitter(jit float64) {
|
||||
if jit < 0 || jit > 1 {
|
||||
return
|
||||
}
|
||||
r.jitter = jit
|
||||
}
|
||||
129
third/github.com/eapache/go-resiliency/retrier/retrier_test.go
Normal file
129
third/github.com/eapache/go-resiliency/retrier/retrier_test.go
Normal file
@ -0,0 +1,129 @@
|
||||
package retrier
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var i int
|
||||
|
||||
func genWork(returns []error) func() error {
|
||||
i = 0
|
||||
return func() error {
|
||||
i++
|
||||
if i > len(returns) {
|
||||
return nil
|
||||
}
|
||||
return returns[i-1]
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetrier(t *testing.T) {
|
||||
r := New([]time.Duration{0, 10 * time.Millisecond}, WhitelistClassifier{errFoo})
|
||||
|
||||
err := r.Run(genWork([]error{errFoo, errFoo}))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if i != 3 {
|
||||
t.Error("run wrong number of times")
|
||||
}
|
||||
|
||||
err = r.Run(genWork([]error{errFoo, errBar}))
|
||||
if err != errBar {
|
||||
t.Error(err)
|
||||
}
|
||||
if i != 2 {
|
||||
t.Error("run wrong number of times")
|
||||
}
|
||||
|
||||
err = r.Run(genWork([]error{errBar, errBaz}))
|
||||
if err != errBar {
|
||||
t.Error(err)
|
||||
}
|
||||
if i != 1 {
|
||||
t.Error("run wrong number of times")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetrierNone(t *testing.T) {
|
||||
r := New(nil, nil)
|
||||
|
||||
i = 0
|
||||
err := r.Run(func() error {
|
||||
i++
|
||||
return errFoo
|
||||
})
|
||||
if err != errFoo {
|
||||
t.Error(err)
|
||||
}
|
||||
if i != 1 {
|
||||
t.Error("run wrong number of times")
|
||||
}
|
||||
|
||||
i = 0
|
||||
err = r.Run(func() error {
|
||||
i++
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if i != 1 {
|
||||
t.Error("run wrong number of times")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetrierJitter(t *testing.T) {
|
||||
r := New([]time.Duration{0, 10 * time.Millisecond, 4 * time.Hour}, nil)
|
||||
|
||||
if r.calcSleep(0) != 0 {
|
||||
t.Error("Incorrect sleep calculated")
|
||||
}
|
||||
if r.calcSleep(1) != 10*time.Millisecond {
|
||||
t.Error("Incorrect sleep calculated")
|
||||
}
|
||||
if r.calcSleep(2) != 4*time.Hour {
|
||||
t.Error("Incorrect sleep calculated")
|
||||
}
|
||||
|
||||
r.SetJitter(0.25)
|
||||
for i := 0; i < 20; i++ {
|
||||
if r.calcSleep(0) != 0 {
|
||||
t.Error("Incorrect sleep calculated")
|
||||
}
|
||||
|
||||
slp := r.calcSleep(1)
|
||||
if slp < 7500*time.Microsecond || slp > 12500*time.Microsecond {
|
||||
t.Error("Incorrect sleep calculated")
|
||||
}
|
||||
|
||||
slp = r.calcSleep(2)
|
||||
if slp < 3*time.Hour || slp > 5*time.Hour {
|
||||
t.Error("Incorrect sleep calculated")
|
||||
}
|
||||
}
|
||||
|
||||
r.SetJitter(-1)
|
||||
if r.jitter != 0.25 {
|
||||
t.Error("Invalid jitter value accepted")
|
||||
}
|
||||
|
||||
r.SetJitter(2)
|
||||
if r.jitter != 0.25 {
|
||||
t.Error("Invalid jitter value accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleRetrier() {
|
||||
r := New(ConstantBackoff(3, 100*time.Millisecond), nil)
|
||||
|
||||
err := r.Run(func() error {
|
||||
// do some work
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
// handle the case where the work failed three times
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user