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  }