github.com/lingyao2333/mo-zero@v1.4.1/core/breaker/breaker.go (about) 1 package breaker 2 3 import ( 4 "errors" 5 "fmt" 6 "strings" 7 "sync" 8 "time" 9 10 "github.com/lingyao2333/mo-zero/core/mathx" 11 "github.com/lingyao2333/mo-zero/core/proc" 12 "github.com/lingyao2333/mo-zero/core/stat" 13 "github.com/lingyao2333/mo-zero/core/stringx" 14 ) 15 16 const ( 17 numHistoryReasons = 5 18 timeFormat = "15:04:05" 19 ) 20 21 // ErrServiceUnavailable is returned when the Breaker state is open. 22 var ErrServiceUnavailable = errors.New("circuit breaker is open") 23 24 type ( 25 // Acceptable is the func to check if the error can be accepted. 26 Acceptable func(err error) bool 27 28 // A Breaker represents a circuit breaker. 29 Breaker interface { 30 // Name returns the name of the Breaker. 31 Name() string 32 33 // Allow checks if the request is allowed. 34 // If allowed, a promise will be returned, the caller needs to call promise.Accept() 35 // on success, or call promise.Reject() on failure. 36 // If not allow, ErrServiceUnavailable will be returned. 37 Allow() (Promise, error) 38 39 // Do runs the given request if the Breaker accepts it. 40 // Do returns an error instantly if the Breaker rejects the request. 41 // If a panic occurs in the request, the Breaker handles it as an error 42 // and causes the same panic again. 43 Do(req func() error) error 44 45 // DoWithAcceptable runs the given request if the Breaker accepts it. 46 // DoWithAcceptable returns an error instantly if the Breaker rejects the request. 47 // If a panic occurs in the request, the Breaker handles it as an error 48 // and causes the same panic again. 49 // acceptable checks if it's a successful call, even if the err is not nil. 50 DoWithAcceptable(req func() error, acceptable Acceptable) error 51 52 // DoWithFallback runs the given request if the Breaker accepts it. 53 // DoWithFallback runs the fallback if the Breaker rejects the request. 54 // If a panic occurs in the request, the Breaker handles it as an error 55 // and causes the same panic again. 56 DoWithFallback(req func() error, fallback func(err error) error) error 57 58 // DoWithFallbackAcceptable runs the given request if the Breaker accepts it. 59 // DoWithFallbackAcceptable runs the fallback if the Breaker rejects the request. 60 // If a panic occurs in the request, the Breaker handles it as an error 61 // and causes the same panic again. 62 // acceptable checks if it's a successful call, even if the err is not nil. 63 DoWithFallbackAcceptable(req func() error, fallback func(err error) error, acceptable Acceptable) error 64 } 65 66 // Option defines the method to customize a Breaker. 67 Option func(breaker *circuitBreaker) 68 69 // Promise interface defines the callbacks that returned by Breaker.Allow. 70 Promise interface { 71 // Accept tells the Breaker that the call is successful. 72 Accept() 73 // Reject tells the Breaker that the call is failed. 74 Reject(reason string) 75 } 76 77 internalPromise interface { 78 Accept() 79 Reject() 80 } 81 82 circuitBreaker struct { 83 name string 84 throttle 85 } 86 87 internalThrottle interface { 88 allow() (internalPromise, error) 89 doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error 90 } 91 92 throttle interface { 93 allow() (Promise, error) 94 doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error 95 } 96 ) 97 98 // NewBreaker returns a Breaker object. 99 // opts can be used to customize the Breaker. 100 func NewBreaker(opts ...Option) Breaker { 101 var b circuitBreaker 102 for _, opt := range opts { 103 opt(&b) 104 } 105 if len(b.name) == 0 { 106 b.name = stringx.Rand() 107 } 108 b.throttle = newLoggedThrottle(b.name, newGoogleBreaker()) 109 110 return &b 111 } 112 113 func (cb *circuitBreaker) Allow() (Promise, error) { 114 return cb.throttle.allow() 115 } 116 117 func (cb *circuitBreaker) Do(req func() error) error { 118 return cb.throttle.doReq(req, nil, defaultAcceptable) 119 } 120 121 func (cb *circuitBreaker) DoWithAcceptable(req func() error, acceptable Acceptable) error { 122 return cb.throttle.doReq(req, nil, acceptable) 123 } 124 125 func (cb *circuitBreaker) DoWithFallback(req func() error, fallback func(err error) error) error { 126 return cb.throttle.doReq(req, fallback, defaultAcceptable) 127 } 128 129 func (cb *circuitBreaker) DoWithFallbackAcceptable(req func() error, fallback func(err error) error, 130 acceptable Acceptable) error { 131 return cb.throttle.doReq(req, fallback, acceptable) 132 } 133 134 func (cb *circuitBreaker) Name() string { 135 return cb.name 136 } 137 138 // WithName returns a function to set the name of a Breaker. 139 func WithName(name string) Option { 140 return func(b *circuitBreaker) { 141 b.name = name 142 } 143 } 144 145 func defaultAcceptable(err error) bool { 146 return err == nil 147 } 148 149 type loggedThrottle struct { 150 name string 151 internalThrottle 152 errWin *errorWindow 153 } 154 155 func newLoggedThrottle(name string, t internalThrottle) loggedThrottle { 156 return loggedThrottle{ 157 name: name, 158 internalThrottle: t, 159 errWin: new(errorWindow), 160 } 161 } 162 163 func (lt loggedThrottle) allow() (Promise, error) { 164 promise, err := lt.internalThrottle.allow() 165 return promiseWithReason{ 166 promise: promise, 167 errWin: lt.errWin, 168 }, lt.logError(err) 169 } 170 171 func (lt loggedThrottle) doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error { 172 return lt.logError(lt.internalThrottle.doReq(req, fallback, func(err error) bool { 173 accept := acceptable(err) 174 if !accept && err != nil { 175 lt.errWin.add(err.Error()) 176 } 177 return accept 178 })) 179 } 180 181 func (lt loggedThrottle) logError(err error) error { 182 if err == ErrServiceUnavailable { 183 // if circuit open, not possible to have empty error window 184 stat.Report(fmt.Sprintf( 185 "proc(%s/%d), callee: %s, breaker is open and requests dropped\nlast errors:\n%s", 186 proc.ProcessName(), proc.Pid(), lt.name, lt.errWin)) 187 } 188 189 return err 190 } 191 192 type errorWindow struct { 193 reasons [numHistoryReasons]string 194 index int 195 count int 196 lock sync.Mutex 197 } 198 199 func (ew *errorWindow) add(reason string) { 200 ew.lock.Lock() 201 ew.reasons[ew.index] = fmt.Sprintf("%s %s", time.Now().Format(timeFormat), reason) 202 ew.index = (ew.index + 1) % numHistoryReasons 203 ew.count = mathx.MinInt(ew.count+1, numHistoryReasons) 204 ew.lock.Unlock() 205 } 206 207 func (ew *errorWindow) String() string { 208 var reasons []string 209 210 ew.lock.Lock() 211 // reverse order 212 for i := ew.index - 1; i >= ew.index-ew.count; i-- { 213 reasons = append(reasons, ew.reasons[(i+numHistoryReasons)%numHistoryReasons]) 214 } 215 ew.lock.Unlock() 216 217 return strings.Join(reasons, "\n") 218 } 219 220 type promiseWithReason struct { 221 promise internalPromise 222 errWin *errorWindow 223 } 224 225 func (p promiseWithReason) Accept() { 226 p.promise.Accept() 227 } 228 229 func (p promiseWithReason) Reject(reason string) { 230 p.errWin.add(reason) 231 p.promise.Reject() 232 }