github.com/lingyao2333/mo-zero@v1.4.1/core/breaker/googlebreaker.go (about)

     1  package breaker
     2  
     3  import (
     4  	"math"
     5  	"time"
     6  
     7  	"github.com/lingyao2333/mo-zero/core/collection"
     8  	"github.com/lingyao2333/mo-zero/core/mathx"
     9  )
    10  
    11  const (
    12  	// 250ms for bucket duration
    13  	window     = time.Second * 10
    14  	buckets    = 40
    15  	k          = 1.5
    16  	protection = 5
    17  )
    18  
    19  // googleBreaker is a netflixBreaker pattern from google.
    20  // see Client-Side Throttling section in https://landing.google.com/sre/sre-book/chapters/handling-overload/
    21  type googleBreaker struct {
    22  	k     float64
    23  	stat  *collection.RollingWindow
    24  	proba *mathx.Proba
    25  }
    26  
    27  func newGoogleBreaker() *googleBreaker {
    28  	bucketDuration := time.Duration(int64(window) / int64(buckets))
    29  	st := collection.NewRollingWindow(buckets, bucketDuration)
    30  	return &googleBreaker{
    31  		stat:  st,
    32  		k:     k,
    33  		proba: mathx.NewProba(),
    34  	}
    35  }
    36  
    37  func (b *googleBreaker) accept() error {
    38  	accepts, total := b.history()
    39  	weightedAccepts := b.k * float64(accepts)
    40  	// https://landing.google.com/sre/sre-book/chapters/handling-overload/#eq2101
    41  	dropRatio := math.Max(0, (float64(total-protection)-weightedAccepts)/float64(total+1))
    42  	if dropRatio <= 0 {
    43  		return nil
    44  	}
    45  
    46  	if b.proba.TrueOnProba(dropRatio) {
    47  		return ErrServiceUnavailable
    48  	}
    49  
    50  	return nil
    51  }
    52  
    53  func (b *googleBreaker) allow() (internalPromise, error) {
    54  	if err := b.accept(); err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	return googlePromise{
    59  		b: b,
    60  	}, nil
    61  }
    62  
    63  func (b *googleBreaker) doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error {
    64  	if err := b.accept(); err != nil {
    65  		if fallback != nil {
    66  			return fallback(err)
    67  		}
    68  
    69  		return err
    70  	}
    71  
    72  	defer func() {
    73  		if e := recover(); e != nil {
    74  			b.markFailure()
    75  			panic(e)
    76  		}
    77  	}()
    78  
    79  	err := req()
    80  	if acceptable(err) {
    81  		b.markSuccess()
    82  	} else {
    83  		b.markFailure()
    84  	}
    85  
    86  	return err
    87  }
    88  
    89  func (b *googleBreaker) markSuccess() {
    90  	b.stat.Add(1)
    91  }
    92  
    93  func (b *googleBreaker) markFailure() {
    94  	b.stat.Add(0)
    95  }
    96  
    97  func (b *googleBreaker) history() (accepts, total int64) {
    98  	b.stat.Reduce(func(b *collection.Bucket) {
    99  		accepts += int64(b.Sum)
   100  		total += b.Count
   101  	})
   102  
   103  	return
   104  }
   105  
   106  type googlePromise struct {
   107  	b *googleBreaker
   108  }
   109  
   110  func (p googlePromise) Accept() {
   111  	p.b.markSuccess()
   112  }
   113  
   114  func (p googlePromise) Reject() {
   115  	p.b.markFailure()
   116  }