github.com/cloudwego/kitex@v0.9.0/pkg/circuitbreak/cbsuite.go (about) 1 /* 2 * Copyright 2021 CloudWeGo Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package circuitbreak 18 19 import ( 20 "context" 21 "fmt" 22 "strings" 23 "sync" 24 "time" 25 26 "github.com/bytedance/gopkg/cloud/circuitbreaker" 27 28 "github.com/cloudwego/kitex/pkg/discovery" 29 "github.com/cloudwego/kitex/pkg/endpoint" 30 "github.com/cloudwego/kitex/pkg/event" 31 "github.com/cloudwego/kitex/pkg/kerrors" 32 "github.com/cloudwego/kitex/pkg/rpcinfo" 33 ) 34 35 const ( 36 serviceCBKey = "service" 37 instanceCBKey = "instance" 38 cbConfig = "cb_config" 39 ) 40 41 var defaultCBConfig = CBConfig{Enable: true, ErrRate: 0.5, MinSample: 200} 42 43 // GetDefaultCBConfig return defaultConfig of CircuitBreaker. 44 func GetDefaultCBConfig() CBConfig { 45 return defaultCBConfig 46 } 47 48 // CBConfig is policy config of CircuitBreaker. 49 // DON'T FORGET to update DeepCopy() and Equals() if you add new fields. 50 type CBConfig struct { 51 Enable bool `json:"enable"` 52 ErrRate float64 `json:"err_rate"` 53 MinSample int64 `json:"min_sample"` 54 } 55 56 // DeepCopy returns a full copy of CBConfig. 57 func (c *CBConfig) DeepCopy() *CBConfig { 58 if c == nil { 59 return nil 60 } 61 return &CBConfig{ 62 Enable: c.Enable, 63 ErrRate: c.ErrRate, 64 MinSample: c.MinSample, 65 } 66 } 67 68 func (c *CBConfig) Equals(other *CBConfig) bool { 69 if c == nil && other == nil { 70 return true 71 } 72 if c == nil || other == nil { 73 return false 74 } 75 return c.Enable == other.Enable && c.ErrRate == other.ErrRate && c.MinSample == other.MinSample 76 } 77 78 // GenServiceCBKeyFunc to generate circuit breaker key through rpcinfo. 79 // You can customize the config key according to your config center. 80 type GenServiceCBKeyFunc func(ri rpcinfo.RPCInfo) string 81 82 type instanceCBConfig struct { 83 CBConfig 84 sync.RWMutex 85 } 86 87 // CBSuite is default wrapper of CircuitBreaker. If you don't have customized policy, you can specify CircuitBreaker 88 // middlewares like this: 89 // 90 // cbs := NewCBSuite(GenServiceCBKeyFunc) 91 // opts = append(opts, client.WithCircuitBreaker(cbs)) 92 type CBSuite struct { 93 servicePanel circuitbreaker.Panel 94 serviceControl *Control 95 instancePanel circuitbreaker.Panel 96 instanceControl *Control 97 98 genServiceCBKey GenServiceCBKeyFunc 99 serviceCBConfig sync.Map // map[serviceCBKey]CBConfig 100 101 instanceCBConfig instanceCBConfig 102 103 events event.Queue 104 } 105 106 // NewCBSuite to build a new CBSuite. 107 // Notice: Should NewCBSuite for every client in this version, 108 // because event.Queue and event.Bus are not shared with all clients now. 109 func NewCBSuite(genKey GenServiceCBKeyFunc) *CBSuite { 110 s := &CBSuite{genServiceCBKey: genKey} 111 s.instanceCBConfig = instanceCBConfig{CBConfig: defaultCBConfig} 112 return s 113 } 114 115 // ServiceCBMW return a new service level CircuitBreakerMW. 116 func (s *CBSuite) ServiceCBMW() endpoint.Middleware { 117 if s == nil { 118 return endpoint.DummyMiddleware 119 } 120 s.initServiceCB() 121 return NewCircuitBreakerMW(*s.serviceControl, s.servicePanel) 122 } 123 124 // InstanceCBMW return a new instance level CircuitBreakerMW. 125 func (s *CBSuite) InstanceCBMW() endpoint.Middleware { 126 if s == nil { 127 return endpoint.DummyMiddleware 128 } 129 s.initInstanceCB() 130 return NewCircuitBreakerMW(*s.instanceControl, s.instancePanel) 131 } 132 133 // ServicePanel return return cb Panel of service 134 func (s *CBSuite) ServicePanel() circuitbreaker.Panel { 135 if s.servicePanel == nil { 136 s.initServiceCB() 137 } 138 return s.servicePanel 139 } 140 141 // ServiceControl return cb Control of service 142 func (s *CBSuite) ServiceControl() *Control { 143 if s.serviceControl == nil { 144 s.initServiceCB() 145 } 146 return s.serviceControl 147 } 148 149 // UpdateServiceCBConfig is to update service CircuitBreaker config. 150 // This func is suggested to be called in remote config module. 151 func (s *CBSuite) UpdateServiceCBConfig(key string, cfg CBConfig) { 152 s.serviceCBConfig.Store(key, cfg) 153 } 154 155 // UpdateInstanceCBConfig is to update instance CircuitBreaker param. 156 // This func is suggested to be called in remote config module. 157 func (s *CBSuite) UpdateInstanceCBConfig(cfg CBConfig) { 158 s.instanceCBConfig.Lock() 159 s.instanceCBConfig.CBConfig = cfg 160 s.instanceCBConfig.Unlock() 161 } 162 163 // SetEventBusAndQueue is to make CircuitBreaker relate to event change. 164 func (s *CBSuite) SetEventBusAndQueue(bus event.Bus, events event.Queue) { 165 s.events = events 166 if bus != nil { 167 bus.Watch(discovery.ChangeEventName, s.discoveryChangeHandler) 168 } 169 } 170 171 // Dump is to dump CircuitBreaker info for debug query. 172 func (s *CBSuite) Dump() interface{} { 173 return map[string]interface{}{ 174 serviceCBKey: cbDebugInfo(s.servicePanel), 175 instanceCBKey: cbDebugInfo(s.instancePanel), 176 cbConfig: s.configInfo(), 177 } 178 } 179 180 // Close circuitbreaker.Panel to release associated resources. 181 func (s *CBSuite) Close() error { 182 if s.servicePanel != nil { 183 s.servicePanel.Close() 184 s.servicePanel = nil 185 s.serviceControl = nil 186 } 187 if s.instancePanel != nil { 188 s.instancePanel.Close() 189 s.instancePanel = nil 190 s.instanceControl = nil 191 } 192 return nil 193 } 194 195 func (s *CBSuite) initServiceCB() { 196 if s.servicePanel != nil && s.serviceControl != nil { 197 return 198 } 199 if s.genServiceCBKey == nil { 200 s.genServiceCBKey = RPCInfo2Key 201 } 202 opts := circuitbreaker.Options{ 203 ShouldTripWithKey: s.svcTripFunc, 204 } 205 s.servicePanel, _ = circuitbreaker.NewPanel(s.onServiceStateChange, opts) 206 207 svcKey := func(ctx context.Context, request interface{}) (serviceCBKey string, enabled bool) { 208 ri := rpcinfo.GetRPCInfo(ctx) 209 serviceCBKey = s.genServiceCBKey(ri) 210 cbConfig, _ := s.serviceCBConfig.LoadOrStore(serviceCBKey, defaultCBConfig) 211 enabled = cbConfig.(CBConfig).Enable 212 return 213 } 214 s.serviceControl = &Control{ 215 GetKey: svcKey, 216 GetErrorType: ErrorTypeOnServiceLevel, 217 DecorateError: func(ctx context.Context, request interface{}, err error) error { 218 return kerrors.ErrServiceCircuitBreak 219 }, 220 } 221 } 222 223 func (s *CBSuite) initInstanceCB() { 224 if s.instancePanel != nil && s.instanceControl != nil { 225 return 226 } 227 opts := circuitbreaker.Options{ 228 ShouldTripWithKey: s.insTripFunc, 229 } 230 s.instancePanel, _ = circuitbreaker.NewPanel(s.onInstanceStateChange, opts) 231 232 instanceKey := func(ctx context.Context, request interface{}) (instCBKey string, enabled bool) { 233 ri := rpcinfo.GetRPCInfo(ctx) 234 instCBKey = ri.To().Address().String() 235 s.instanceCBConfig.RLock() 236 enabled = s.instanceCBConfig.Enable 237 s.instanceCBConfig.RUnlock() 238 return 239 } 240 s.instanceControl = &Control{ 241 GetKey: instanceKey, 242 GetErrorType: ErrorTypeOnInstanceLevel, 243 DecorateError: func(ctx context.Context, request interface{}, err error) error { 244 return kerrors.ErrInstanceCircuitBreak 245 }, 246 } 247 } 248 249 func (s *CBSuite) onStateChange(level, key string, oldState, newState circuitbreaker.State, m circuitbreaker.Metricer) { 250 if s.events == nil { 251 return 252 } 253 successes, failures, timeouts := m.Counts() 254 var errRate float64 255 if sum := successes + failures + timeouts; sum > 0 { 256 errRate = float64(failures+timeouts) / float64(sum) 257 } 258 s.events.Push(&event.Event{ 259 Name: level + "_cb", 260 Time: time.Now(), 261 Detail: fmt.Sprintf("%s: %s -> %s, (succ: %d, err: %d, timeout: %d, rate: %f)", 262 key, oldState, newState, successes, failures, timeouts, errRate), 263 }) 264 } 265 266 func (s *CBSuite) onServiceStateChange(key string, oldState, newState circuitbreaker.State, m circuitbreaker.Metricer) { 267 s.onStateChange(serviceCBKey, key, oldState, newState, m) 268 } 269 270 func (s *CBSuite) onInstanceStateChange(key string, oldState, newState circuitbreaker.State, m circuitbreaker.Metricer) { 271 s.onStateChange(instanceCBKey, key, oldState, newState, m) 272 } 273 274 func (s *CBSuite) discoveryChangeHandler(e *event.Event) { 275 if s.instancePanel == nil { 276 return 277 } 278 extra := e.Extra.(*discovery.Change) 279 for i := range extra.Removed { 280 instCBKey := extra.Removed[i].Address().String() 281 s.instancePanel.RemoveBreaker(instCBKey) 282 } 283 } 284 285 func (s *CBSuite) svcTripFunc(key string) circuitbreaker.TripFunc { 286 pi, _ := s.serviceCBConfig.LoadOrStore(key, defaultCBConfig) 287 p := pi.(CBConfig) 288 return circuitbreaker.RateTripFunc(p.ErrRate, p.MinSample) 289 } 290 291 func (s *CBSuite) insTripFunc(key string) circuitbreaker.TripFunc { 292 s.instanceCBConfig.RLock() 293 errRate := s.instanceCBConfig.ErrRate 294 minSample := s.instanceCBConfig.MinSample 295 s.instanceCBConfig.RUnlock() 296 return circuitbreaker.RateTripFunc(errRate, minSample) 297 } 298 299 func cbDebugInfo(panel circuitbreaker.Panel) map[string]interface{} { 300 dumper, ok := panel.(interface { 301 DumpBreakers() map[string]circuitbreaker.Breaker 302 }) 303 if !ok { 304 return nil 305 } 306 cbMap := make(map[string]interface{}) 307 for key, breaker := range dumper.DumpBreakers() { 308 cbState := breaker.State() 309 if cbState == circuitbreaker.Closed { 310 continue 311 } 312 cbMap[key] = map[string]interface{}{ 313 "state": cbState, 314 "successes in 10s": breaker.Metricer().Successes(), 315 "failures in 10s": breaker.Metricer().Failures(), 316 "timeouts in 10s": breaker.Metricer().Timeouts(), 317 "error rate in 10s": breaker.Metricer().ErrorRate(), 318 } 319 } 320 if len(cbMap) == 0 { 321 cbMap["msg"] = "all circuit breakers are in closed state" 322 } 323 return cbMap 324 } 325 326 func (s *CBSuite) configInfo() map[string]interface{} { 327 svcCBMap := make(map[string]interface{}) 328 s.serviceCBConfig.Range(func(key, value interface{}) bool { 329 svcCBMap[key.(string)] = value 330 return true 331 }) 332 s.instanceCBConfig.RLock() 333 instCBConfig := s.instanceCBConfig.CBConfig 334 s.instanceCBConfig.RUnlock() 335 336 cbMap := make(map[string]interface{}, 2) 337 cbMap[serviceCBKey] = svcCBMap 338 cbMap[instanceCBKey] = instCBConfig 339 return cbMap 340 } 341 342 // RPCInfo2Key is to generate circuit breaker key through rpcinfo 343 func RPCInfo2Key(ri rpcinfo.RPCInfo) string { 344 if ri == nil { 345 return "" 346 } 347 fromService := ri.From().ServiceName() 348 toService := ri.To().ServiceName() 349 method := ri.To().Method() 350 351 sum := len(fromService) + len(toService) + len(method) + 2 352 var buf strings.Builder 353 buf.Grow(sum) 354 buf.WriteString(fromService) 355 buf.WriteByte('/') 356 buf.WriteString(toService) 357 buf.WriteByte('/') 358 buf.WriteString(method) 359 return buf.String() 360 }