github.com/bytedance/gopkg@v0.0.0-20240514070511-01b2cbcf35e1/cloud/circuitbreaker/panel.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" 19 "time" 20 21 "github.com/bytedance/gopkg/collection/skipmap" 22 ) 23 24 // panel manages a batch of circuitbreakers 25 type panel struct { 26 breakers *skipmap.StringMap 27 defaultOptions Options 28 changeHandler PanelStateChangeHandler 29 } 30 31 type sharedTicker struct { 32 sync.Mutex 33 started bool 34 stopChan chan bool 35 ticker *time.Ticker 36 panels map[*panel]struct{} 37 } 38 39 var tickerMap sync.Map // 共用 ticker 40 41 // NewPanel . 42 func NewPanel(changeHandler PanelStateChangeHandler, 43 defaultOptions Options) (Panel, error) { 44 if defaultOptions.BucketTime <= 0 { 45 defaultOptions.BucketTime = defaultBucketTime 46 } 47 48 if defaultOptions.BucketNums <= 0 { 49 defaultOptions.BucketNums = defaultBucketNums 50 } 51 52 if defaultOptions.CoolingTimeout <= 0 { 53 defaultOptions.CoolingTimeout = defaultCoolingTimeout 54 } 55 56 if defaultOptions.DetectTimeout <= 0 { 57 defaultOptions.DetectTimeout = defaultDetectTimeout 58 } 59 _, err := newBreaker(defaultOptions) 60 if err != nil { 61 return nil, err 62 } 63 p := &panel{ 64 breakers: skipmap.NewString(), 65 defaultOptions: defaultOptions, 66 changeHandler: changeHandler, 67 } 68 ti, _ := tickerMap.LoadOrStore(p.defaultOptions.BucketTime, 69 &sharedTicker{panels: make(map[*panel]struct{}), stopChan: make(chan bool, 1)}) 70 t := ti.(*sharedTicker) 71 t.Lock() 72 t.panels[p] = struct{}{} 73 if !t.started { 74 t.started = true 75 t.ticker = time.NewTicker(p.defaultOptions.BucketTime) 76 go t.tick(t.ticker) 77 } 78 t.Unlock() 79 return p, nil 80 } 81 82 // getBreaker . 83 func (p *panel) getBreaker(key string) *breaker { 84 cb, ok := p.breakers.Load(key) 85 if ok { 86 return cb.(*breaker) 87 } 88 89 op := p.defaultOptions 90 if p.changeHandler != nil { 91 op.BreakerStateChangeHandler = func(oldState, newState State, m Metricer) { 92 p.changeHandler(key, oldState, newState, m) 93 } 94 } 95 ncb, _ := newBreaker(op) 96 cb, ok = p.breakers.LoadOrStore(key, ncb) 97 return cb.(*breaker) 98 } 99 100 // RemoveBreaker . 101 func (p *panel) RemoveBreaker(key string) { 102 p.breakers.Delete(key) 103 } 104 105 // DumpBreakers . 106 func (p *panel) DumpBreakers() map[string]Breaker { 107 breakers := make(map[string]Breaker) 108 p.breakers.Range(func(key string, value interface{}) bool { 109 breakers[key] = value.(*breaker) 110 return true 111 }) 112 return breakers 113 } 114 115 // Succeed . 116 func (p *panel) Succeed(key string) { 117 p.getBreaker(key).Succeed() 118 } 119 120 // Fail . 121 func (p *panel) Fail(key string) { 122 b := p.getBreaker(key) 123 if p.defaultOptions.ShouldTripWithKey != nil { 124 b.FailWithTrip(p.defaultOptions.ShouldTripWithKey(key)) 125 } else { 126 b.Fail() 127 } 128 } 129 130 // FailWithTrip . 131 func (p *panel) FailWithTrip(key string, f TripFunc) { 132 p.getBreaker(key).FailWithTrip(f) 133 } 134 135 // Timeout . 136 func (p *panel) Timeout(key string) { 137 b := p.getBreaker(key) 138 if p.defaultOptions.ShouldTripWithKey != nil { 139 b.TimeoutWithTrip(p.defaultOptions.ShouldTripWithKey(key)) 140 } else { 141 b.Timeout() 142 } 143 } 144 145 // TimeoutWithTrip . 146 func (p *panel) TimeoutWithTrip(key string, f TripFunc) { 147 p.getBreaker(key).TimeoutWithTrip(f) 148 } 149 150 // IsAllowed . 151 func (p *panel) IsAllowed(key string) bool { 152 return p.getBreaker(key).IsAllowed() 153 } 154 155 // GetMetricer ... 156 func (p *panel) GetMetricer(key string) Metricer { 157 return p.getBreaker(key).Metricer() 158 } 159 160 func (p *panel) Close() { 161 ti, _ := tickerMap.Load(p.defaultOptions.BucketTime) 162 t := ti.(*sharedTicker) 163 t.Lock() 164 delete(t.panels, p) 165 if len(t.panels) == 0 { 166 t.stopChan <- true 167 t.started = false 168 } 169 t.Unlock() 170 } 171 172 // tick . 173 // pass ticker but not use t.ticker directly is to ignore race. 174 func (t *sharedTicker) tick(ticker *time.Ticker) { 175 defer ticker.Stop() 176 for { 177 select { 178 case <-ticker.C: 179 t.Lock() 180 for p := range t.panels { 181 p.breakers.Range(func(_ string, value interface{}) bool { 182 if b, ok := value.(*breaker); ok { 183 b.metricer.tick() 184 } 185 return true 186 }) 187 } 188 t.Unlock() 189 case stop := <-t.stopChan: 190 if stop { 191 return 192 } 193 } 194 } 195 }