github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/utils/retry/breaker.go (about)

     1  package retry
     2  
     3  import (
     4  	"sync"
     5  	"time"
     6  )
     7  
     8  const windowSize = 10
     9  const rollingRetryThreshold = 30
    10  
    11  var breakerMap sync.Map
    12  
    13  type breakerKey struct {
    14  	name          string
    15  	overloadRatio float64
    16  }
    17  
    18  func getBreaker(name string, overloadRatio float64) *breaker {
    19  	key := breakerKey{
    20  		name:          name,
    21  		overloadRatio: overloadRatio,
    22  	}
    23  	w, ok := breakerMap.Load(key)
    24  	if !ok {
    25  		w, _ = breakerMap.LoadOrStore(key, &breaker{
    26  			ratio: overloadRatio,
    27  		})
    28  	}
    29  	return w.(*breaker)
    30  }
    31  
    32  type breaker struct {
    33  	ratio float64
    34  	succ  bucket
    35  	fail  bucket
    36  }
    37  
    38  func (b *breaker) shouldRetry() bool {
    39  	nowUnix := time.Now().Unix()
    40  	count := b.fail.sum(nowUnix)
    41  	if count > rollingRetryThreshold &&
    42  		count > b.ratio*b.succ.sum(nowUnix) {
    43  		return false
    44  	}
    45  	return true
    46  }
    47  
    48  type bucket struct {
    49  	mu    sync.RWMutex
    50  	index [windowSize]int64
    51  	count [windowSize]float64
    52  }
    53  
    54  func (b *bucket) incr(nowUnix int64) {
    55  	idx := nowUnix % windowSize
    56  	b.mu.Lock()
    57  	if b.index[idx] != nowUnix {
    58  		b.index[idx] = nowUnix
    59  		b.count[idx] = 0
    60  	}
    61  	b.count[idx]++
    62  	b.mu.Unlock()
    63  }
    64  
    65  func (b *bucket) sum(nowUnix int64) float64 {
    66  	var sum float64
    67  	threshold := nowUnix - windowSize
    68  	b.mu.RLock()
    69  	for i := 0; i < windowSize; i++ {
    70  		if b.index[i] >= threshold {
    71  			sum += b.count[i]
    72  		}
    73  	}
    74  	b.mu.RUnlock()
    75  	return sum
    76  }