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 }