github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/utils/retry/options.go (about)

     1  package retry
     2  
     3  import "time"
     4  
     5  var (
     6  	defaultOptions = options{
     7  		Strategy:  exp,
     8  		MaxErrors: 5,
     9  
    10  		// default 50% jitter
    11  		Jitter: func(d time.Duration) time.Duration {
    12  			return addJitter(d, 0.5)
    13  		},
    14  		// default no-op hook
    15  		Hook: func(attempts int, err error) {}, // dummy no-op hook
    16  	}
    17  )
    18  
    19  type options struct {
    20  	Attempts  int
    21  	Sleep     time.Duration
    22  	MaxSleep  time.Duration
    23  	Strategy  strategy
    24  	Jitter    strategy
    25  	MaxErrors int
    26  	Hook      func(attempts int, err error)
    27  	Breaker   *breaker
    28  }
    29  
    30  type Option func(options) options
    31  
    32  // MaxSleep will restrict the retry sleep time to at most max.
    33  func MaxSleep(max time.Duration) Option {
    34  	return func(opt options) options {
    35  		opt.MaxSleep = max
    36  		return opt
    37  	}
    38  }
    39  
    40  // MaxErrors set max errors to hold when retry for many times.
    41  func MaxErrors(max int) Option {
    42  	return func(opt options) options {
    43  		opt.MaxErrors = max
    44  		return opt
    45  	}
    46  }
    47  
    48  // Hook let the retry function call the given hook when an error happens.
    49  func Hook(hook func(attempts int, err error)) Option {
    50  	return func(opt options) options {
    51  		opt.Hook = hook
    52  		return opt
    53  	}
    54  }
    55  
    56  // NoJitter disables the retry function to add jitter to sleep time between each retry.
    57  func NoJitter() Option {
    58  	return func(opt options) options {
    59  		opt.Jitter = nil
    60  		return opt
    61  	}
    62  }
    63  
    64  // J makes the retry function use specified jitter between each retry.
    65  func J(jitter float64) Option {
    66  	return func(opt options) options {
    67  		opt.Jitter = func(d time.Duration) time.Duration {
    68  			return addJitter(d, jitter)
    69  		}
    70  		return opt
    71  	}
    72  }
    73  
    74  // C makes the retry function sleep constant time between each retry.
    75  func C() Option {
    76  	return func(opt options) options {
    77  		opt.Strategy = constant
    78  		return opt
    79  	}
    80  }
    81  
    82  // L makes the retry function sleep linear growing time between each retry.
    83  func L(step time.Duration) Option {
    84  	l := linear{step: step}
    85  	return func(opt options) options {
    86  		opt.Strategy = l.next
    87  		return opt
    88  	}
    89  }
    90  
    91  // Breaker uses sliding window algorithm to protect system from overload
    92  // with default overload ratio 0.1 (10%).
    93  //
    94  // To prevent overload, Google SRE has some recommendations:
    95  //
    96  // First, we implement a per-request retry budget of up to three attempts.
    97  // If a request has already failed three times, we let the failure bubble
    98  // up to the caller. The rationale is that if a request has already landed
    99  // on overloaded tasks three times, it's relatively unlikely that attempting
   100  // it again will help because the whole datacenter is likely overloaded.
   101  //
   102  // Secondly, we implement a per-client retry budget. Each client keeps track
   103  // of the ratio of requests that correspond to retries. A request will only
   104  // be retried as long as this ratio is below 10%. The rationale is that if
   105  // only a small subset of tasks are overloaded, there will be relatively
   106  // little need to retry.
   107  //
   108  // A third approach has clients include a counter of how many times the
   109  // request has already been tried in the request metadata. For instance,
   110  // the counter starts at 0 in the first attempt and is incremented on every
   111  // retry until it reaches 2, at which point the per-request budget causes
   112  // it to stop being retried. Backends keep histograms of these values in
   113  // recent history. When a backend needs to reject a request, it consults
   114  // these histograms to determine the likelihood that other backend tasks
   115  // are also overloaded. If these histograms reveal a significant amount of
   116  // retries (indicating that other backend tasks are likely also overloaded),
   117  // they return an "overloaded; don't retry" error response instead of the
   118  // standard "task overloaded" error that triggers retries.
   119  //
   120  // Reference: https://sre.google/sre-book/handling-overload/
   121  func Breaker(name string) Option {
   122  	return BreakerWithOverloadRatio(name, 0.1)
   123  }
   124  
   125  // BreakerWithOverloadRatio is similar to Breaker, excepts that it
   126  // accepts an additional param `overloadRatio` to specify the overload
   127  // ratio to control the retry behavior, it's value should be greater
   128  // than zero, else the default value 0.1 will be used.
   129  //
   130  // NOTE: generally, the default overload ratio 0.1 or even smaller value
   131  // should be used, a big overload ratio will not really protect the
   132  // backend system.
   133  //
   134  // Reference: https://sre.google/sre-book/handling-overload/
   135  func BreakerWithOverloadRatio(name string, overloadRatio float64) Option {
   136  	if overloadRatio <= 0 {
   137  		overloadRatio = 0.1
   138  	}
   139  	return func(opt options) options {
   140  		opt.Breaker = getBreaker(name, overloadRatio)
   141  		return opt
   142  	}
   143  }