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 }