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  }