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  }