github.com/bytedance/gopkg@v0.0.0-20240514070511-01b2cbcf35e1/cloud/circuitbreaker/breaker.go (about) 1 // Copyright 2021 ByteDance Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package circuitbreaker 16 17 import ( 18 "sync/atomic" 19 "time" 20 21 "github.com/bytedance/gopkg/lang/syncx" 22 ) 23 24 const ( 25 // cooling timeout is the time the breaker stay in Open before becoming HalfOpen 26 defaultCoolingTimeout = time.Second * 5 27 28 // detect timeout is the time interval between every detect in HalfOpen 29 defaultDetectTimeout = time.Millisecond * 200 30 31 // halfopen success is the threshold when the breaker is in HalfOpen; 32 // after exceeding consecutively this times, it will change its State from HalfOpen to Closed; 33 defaultHalfOpenSuccesses = 2 34 ) 35 36 // breaker is the base of a circuit breaker. 37 type breaker struct { 38 rw syncx.RWMutex 39 40 metricer metricer // metrics all success, error and timeout within some time 41 42 state State // State now 43 openTime time.Time // the time when the breaker become Open recently 44 lastRetryTime time.Time // last retry time when in HalfOpen State 45 halfopenSuccess int32 // consecutive successes when HalfOpen 46 isFixed bool 47 48 options Options 49 50 now func() time.Time // Default value is time.Now, caller may use some high-performance custom time now func here 51 } 52 53 // newBreaker creates a base breaker with a specified options 54 func newBreaker(options Options) (*breaker, error) { 55 if options.Now == nil { 56 options.Now = time.Now 57 } 58 59 if options.BucketTime <= 0 { 60 options.BucketTime = defaultBucketTime 61 } 62 63 if options.BucketNums <= 0 { 64 options.BucketNums = defaultBucketNums 65 } 66 67 if options.CoolingTimeout <= 0 { 68 options.CoolingTimeout = defaultCoolingTimeout 69 } 70 71 if options.DetectTimeout <= 0 { 72 options.DetectTimeout = defaultDetectTimeout 73 } 74 75 if options.HalfOpenSuccesses <= 0 { 76 options.HalfOpenSuccesses = defaultHalfOpenSuccesses 77 } 78 79 var window metricer 80 var err error 81 if options.EnableShardP { 82 window, err = newPerPWindowWithOptions(options.BucketTime, options.BucketNums) 83 } else { 84 window, err = newWindowWithOptions(options.BucketTime, options.BucketNums) 85 } 86 if err != nil { 87 return nil, err 88 } 89 90 breaker := &breaker{ 91 rw: syncx.NewRWMutex(), 92 metricer: window, 93 now: options.Now, 94 state: Closed, 95 } 96 97 breaker.options = Options{ 98 BucketTime: options.BucketTime, 99 BucketNums: options.BucketNums, 100 CoolingTimeout: options.CoolingTimeout, 101 DetectTimeout: options.DetectTimeout, 102 HalfOpenSuccesses: options.HalfOpenSuccesses, 103 ShouldTrip: options.ShouldTrip, 104 ShouldTripWithKey: options.ShouldTripWithKey, 105 BreakerStateChangeHandler: options.BreakerStateChangeHandler, 106 Now: options.Now, 107 } 108 109 return breaker, nil 110 } 111 112 // Succeed records a success and decreases the concurrency counter by one 113 func (b *breaker) Succeed() { 114 rwx := b.rw.RLocker() 115 rwx.Lock() 116 switch b.State() { 117 case Open: // do nothing 118 rwx.Unlock() 119 case HalfOpen: 120 rwx.Unlock() 121 b.rw.Lock() 122 // 双重检查 State,防止执行两次 BreakerStateChangeHandler 123 if b.State() == HalfOpen { 124 atomic.AddInt32(&b.halfopenSuccess, 1) 125 if atomic.LoadInt32(&b.halfopenSuccess) >= b.options.HalfOpenSuccesses { 126 if b.options.BreakerStateChangeHandler != nil { 127 go b.options.BreakerStateChangeHandler(HalfOpen, Closed, b.metricer) 128 } 129 b.metricer.Reset() 130 atomic.StoreInt32((*int32)(&b.state), int32(Closed)) 131 } 132 } 133 b.rw.Unlock() 134 case Closed: 135 b.metricer.Succeed() 136 rwx.Unlock() 137 } 138 } 139 140 func (b *breaker) error(isTimeout bool, trip TripFunc) { 141 rwx := b.rw.RLocker() 142 rwx.Lock() 143 if isTimeout { 144 b.metricer.Timeout() 145 } else { 146 b.metricer.Fail() 147 } 148 149 switch b.State() { 150 case Open: // do nothing 151 rwx.Unlock() 152 case HalfOpen: // become Open 153 rwx.Unlock() 154 b.rw.Lock() 155 // 双重检查 State,防止执行两次 BreakerStateChangeHandler 156 if b.State() == HalfOpen { 157 if b.options.BreakerStateChangeHandler != nil { 158 go b.options.BreakerStateChangeHandler(HalfOpen, Open, b.metricer) 159 } 160 b.openTime = b.now() 161 atomic.StoreInt32((*int32)(&b.state), int32(Open)) 162 } 163 b.rw.Unlock() 164 case Closed: // call ShouldTrip 165 if trip != nil && trip(b.metricer) { 166 rwx.Unlock() 167 b.rw.Lock() 168 if b.State() == Closed { 169 // become Open and set the Open time 170 if b.options.BreakerStateChangeHandler != nil { 171 go b.options.BreakerStateChangeHandler(Closed, Open, b.metricer) 172 } 173 b.openTime = b.now() 174 atomic.StoreInt32((*int32)(&b.state), int32(Open)) 175 } 176 b.rw.Unlock() 177 } else { 178 rwx.Unlock() 179 } 180 } 181 } 182 183 // Fail records a failure and decreases the concurrency counter by one 184 func (b *breaker) Fail() { 185 b.error(false, b.options.ShouldTrip) 186 } 187 188 // FailWithTrip . 189 func (b *breaker) FailWithTrip(trip TripFunc) { 190 b.error(false, trip) 191 } 192 193 // Timeout records a timeout and decreases the concurrency counter by one 194 func (b *breaker) Timeout() { 195 b.error(true, b.options.ShouldTrip) 196 } 197 198 // TimeoutWithTrip . 199 func (b *breaker) TimeoutWithTrip(trip TripFunc) { 200 b.error(true, trip) 201 } 202 203 // IsAllowed . 204 func (b *breaker) IsAllowed() bool { 205 return b.isAllowed() 206 } 207 208 // IsAllowed . 209 func (b *breaker) isAllowed() bool { 210 rwx := b.rw.RLocker() 211 rwx.Lock() 212 switch b.State() { 213 case Open: 214 now := b.now() 215 if b.openTime.Add(b.options.CoolingTimeout).After(now) { 216 rwx.Unlock() 217 return false 218 } 219 rwx.Unlock() 220 b.rw.Lock() 221 if b.State() == Open { 222 // cooling timeout, then become HalfOpen 223 if b.options.BreakerStateChangeHandler != nil { 224 go b.options.BreakerStateChangeHandler(Open, HalfOpen, b.metricer) 225 } 226 atomic.StoreInt32((*int32)(&b.state), int32(HalfOpen)) 227 atomic.StoreInt32(&b.halfopenSuccess, 0) 228 b.lastRetryTime = now 229 b.rw.Unlock() 230 } else { 231 // other request has changed the state, so we reject current request 232 b.rw.Unlock() 233 return false 234 } 235 case HalfOpen: 236 now := b.now() 237 if b.lastRetryTime.Add(b.options.DetectTimeout).After(now) { 238 rwx.Unlock() 239 return false 240 } 241 rwx.Unlock() 242 b.rw.Lock() 243 if b.State() == HalfOpen { 244 b.lastRetryTime = now 245 } else if b.State() == Open { // callback may change the state to open 246 b.rw.Unlock() 247 return false 248 } 249 b.rw.Unlock() 250 case Closed: 251 rwx.Unlock() 252 } 253 254 return true 255 } 256 257 // State returns the breaker's State now 258 func (b *breaker) State() State { 259 return State(atomic.LoadInt32((*int32)(&b.state))) 260 } 261 262 // Metricer returns the breaker's Metricer 263 func (b *breaker) Metricer() Metricer { 264 return b.metricer 265 } 266 267 // Reset resets this breaker 268 func (b *breaker) Reset() { 269 b.rw.Lock() 270 b.metricer.Reset() 271 atomic.StoreInt32((*int32)(&b.state), int32(Closed)) 272 // don't change concurrency counter anyway 273 b.rw.Unlock() 274 }