github.com/songzhibin97/gkit@v1.2.13/overload/bbr/bbr.go (about)

     1  package bbr
     2  
     3  import (
     4  	"context"
     5  	"math"
     6  	"sync/atomic"
     7  	"time"
     8  
     9  	"github.com/songzhibin97/gkit/container/group"
    10  	"github.com/songzhibin97/gkit/internal/stat"
    11  	cupstat "github.com/songzhibin97/gkit/internal/sys/cpu"
    12  	"github.com/songzhibin97/gkit/log"
    13  	"github.com/songzhibin97/gkit/options"
    14  	"github.com/songzhibin97/gkit/overload"
    15  )
    16  
    17  // package bbr: bbr 限流
    18  
    19  var (
    20  	cpu int64
    21  
    22  	// decay: 衰变周期
    23  	decay = 0.95
    24  
    25  	// initTime: 起始时间
    26  	initTime = time.Now()
    27  )
    28  
    29  // cpuGetter:
    30  type cpuGetter func() int64
    31  
    32  // config: bbr 配置
    33  type config struct {
    34  	debug        bool
    35  	enabled      bool
    36  	winBucket    int
    37  	cPUThreshold int64
    38  	window       time.Duration
    39  	rule         string
    40  }
    41  
    42  // Stat bbr 指标信息
    43  type Stat struct {
    44  	Cpu         int64
    45  	InFlight    int64
    46  	MaxInFlight int64
    47  	MinRt       int64
    48  	MaxPass     int64
    49  }
    50  
    51  // BBR 实现类似bbr的限制器.
    52  type BBR struct {
    53  	cpu             cpuGetter
    54  	passStat        stat.RollingCounter
    55  	rtStat          stat.RollingCounter
    56  	inFlight        int64
    57  	winBucketPerSec int64
    58  	conf            *config
    59  	prevDrop        atomic.Value
    60  	prevDropHit     int32
    61  	rawMaxPASS      int64
    62  	rawMinRt        int64
    63  }
    64  
    65  // Group 表示BBRLimiter的类,并形成其中的命名空间
    66  type Group struct {
    67  	group group.LazyLoadGroup
    68  }
    69  
    70  // init 启动后台收集cpu信息
    71  func init() {
    72  	go cpuProc()
    73  }
    74  
    75  // cpuProc  定时任务,收集当前服务器CPU信息
    76  // cpu = cpuᵗ⁻¹ * decay + cpuᵗ * (1 - decay)
    77  func cpuProc() {
    78  	ticker := time.NewTicker(time.Millisecond * 250)
    79  	defer func() {
    80  		ticker.Stop()
    81  		if err := recover(); err != nil {
    82  			log.NewHelper(log.DefaultLogger).Errorf("rate.limit.cpuproc() err(%+v)", err)
    83  			go cpuProc()
    84  		}
    85  	}()
    86  
    87  	for range ticker.C {
    88  		stats := &cupstat.Stat{}
    89  		cupstat.ReadStat(stats)
    90  		stats.Usage = min(stats.Usage, 1000)
    91  		prevCpu := atomic.LoadInt64(&cpu)
    92  		curCpu := int64(float64(prevCpu)*decay + float64(stats.Usage)*(1.0-decay))
    93  		atomic.StoreInt64(&cpu, curCpu)
    94  	}
    95  }
    96  
    97  func min(l, r uint64) uint64 {
    98  	if l < r {
    99  		return l
   100  	}
   101  	return r
   102  }
   103  
   104  // maxPASS 最大通过值
   105  func (l *BBR) maxPASS() int64 {
   106  	rawMaxPass := atomic.LoadInt64(&l.rawMaxPASS)
   107  	if rawMaxPass > 0 && l.passStat.Timespan() < 1 {
   108  		return rawMaxPass
   109  	}
   110  	rawMaxPass = int64(l.passStat.Reduce(func(iterator stat.Iterator) float64 {
   111  		result := 1.0
   112  		for i := 1; iterator.Next() && i < l.conf.winBucket; i++ {
   113  			bucket := iterator.Bucket()
   114  			count := 0.0
   115  			for _, p := range bucket.Points {
   116  				count += p
   117  			}
   118  			result = math.Max(result, count)
   119  		}
   120  		return result
   121  	}))
   122  	if rawMaxPass == 0 {
   123  		rawMaxPass = 1
   124  	}
   125  	atomic.StoreInt64(&l.rawMaxPASS, rawMaxPass)
   126  	return rawMaxPass
   127  }
   128  
   129  // minRT 最小RT
   130  func (l *BBR) minRT() int64 {
   131  	rawMinRT := atomic.LoadInt64(&l.rawMinRt)
   132  	if rawMinRT > 0 && l.rtStat.Timespan() < 1 {
   133  		return rawMinRT
   134  	}
   135  	rawMinRT = int64(math.Ceil(l.rtStat.Reduce(func(iterator stat.Iterator) float64 {
   136  		result := math.MaxFloat64
   137  		for i := 1; iterator.Next() && i < l.conf.winBucket; i++ {
   138  			bucket := iterator.Bucket()
   139  			if len(bucket.Points) == 0 {
   140  				continue
   141  			}
   142  			total := 0.0
   143  			for _, p := range bucket.Points {
   144  				total += p
   145  			}
   146  			avg := total / float64(bucket.Count)
   147  			result = math.Min(result, avg)
   148  		}
   149  		return result
   150  	})))
   151  	if rawMinRT <= 0 {
   152  		rawMinRT = 1
   153  	}
   154  	atomic.StoreInt64(&l.rawMinRt, rawMinRT)
   155  	return rawMinRT
   156  }
   157  
   158  // maxFlight
   159  func (l *BBR) maxFlight() int64 {
   160  	return int64(math.Floor(float64(l.maxPASS()*l.minRT()*l.winBucketPerSec)/1000.0 + 0.5))
   161  }
   162  
   163  // shouldDrop 判断是否应该降低
   164  func (l *BBR) shouldDrop() bool {
   165  	if l.cpu() < l.conf.cPUThreshold {
   166  		prevDrop, _ := l.prevDrop.Load().(time.Duration)
   167  		if prevDrop == 0 {
   168  			return false
   169  		}
   170  		if time.Since(initTime)-prevDrop <= time.Second {
   171  			if atomic.LoadInt32(&l.prevDropHit) == 0 {
   172  				atomic.StoreInt32(&l.prevDropHit, 1)
   173  			}
   174  			inFlight := atomic.LoadInt64(&l.inFlight)
   175  			return inFlight > 1 && inFlight > l.maxFlight()
   176  		}
   177  		l.prevDrop.Store(time.Duration(0))
   178  		return false
   179  	}
   180  	inFlight := atomic.LoadInt64(&l.inFlight)
   181  	drop := inFlight > 1 && inFlight > l.maxFlight()
   182  	if drop {
   183  		prevDrop, _ := l.prevDrop.Load().(time.Duration)
   184  		if prevDrop != 0 {
   185  			return drop
   186  		}
   187  		l.prevDrop.Store(time.Since(initTime))
   188  	}
   189  	return drop
   190  }
   191  
   192  // Stat 状态信息
   193  func (l *BBR) Stat() Stat {
   194  	return Stat{
   195  		Cpu:         l.cpu(),
   196  		InFlight:    atomic.LoadInt64(&l.inFlight),
   197  		MinRt:       l.minRT(),
   198  		MaxPass:     l.maxPASS(),
   199  		MaxInFlight: l.maxFlight(),
   200  	}
   201  }
   202  
   203  // Allow 检查所有入站流量
   204  // 一旦检测到过载,它将引发 LimitExceed 错误。
   205  func (l *BBR) Allow(ctx context.Context, opts ...overload.AllowOption) (func(info overload.DoneInfo), error) {
   206  	allowOpts := overload.DefaultAllowOpts()
   207  	for _, opt := range opts {
   208  		opt.Apply(&allowOpts)
   209  	}
   210  	if l.shouldDrop() {
   211  		return nil, LimitExceed
   212  	}
   213  	atomic.AddInt64(&l.inFlight, 1)
   214  	sTime := time.Since(initTime)
   215  	return func(do overload.DoneInfo) {
   216  		if rt := int64((time.Since(initTime) - sTime) / time.Millisecond); rt > 0 {
   217  			l.rtStat.Add(rt)
   218  		}
   219  		atomic.AddInt64(&l.inFlight, -1)
   220  		switch do.Op {
   221  		case overload.Success:
   222  			l.passStat.Add(1)
   223  			return
   224  		default:
   225  			return
   226  		}
   227  	}, nil
   228  }
   229  
   230  // defaultConf 默认配置
   231  func defaultConf() *config {
   232  	return &config{
   233  		// window: 窗口周期
   234  		window:    time.Second * 10,
   235  		winBucket: 100,
   236  		// cPUThreshold: 阈值
   237  		cPUThreshold: 800,
   238  	}
   239  }
   240  
   241  // Option
   242  
   243  func SetDebug(debug bool) options.Option {
   244  	return func(c interface{}) {
   245  		c.(*config).debug = debug
   246  	}
   247  }
   248  
   249  func SetEnabled(enabled bool) options.Option {
   250  	return func(c interface{}) {
   251  		c.(*config).enabled = enabled
   252  	}
   253  }
   254  
   255  func SetWinBucket(winBucket int) options.Option {
   256  	return func(c interface{}) {
   257  		c.(*config).winBucket = winBucket
   258  	}
   259  }
   260  
   261  func SetCPUThreshold(cPUThreshold int64) options.Option {
   262  	return func(c interface{}) {
   263  		c.(*config).cPUThreshold = cPUThreshold
   264  	}
   265  }
   266  
   267  func SetWindow(window time.Duration) options.Option {
   268  	return func(c interface{}) {
   269  		c.(*config).window = window
   270  	}
   271  }
   272  
   273  func SetRule(rule string) options.Option {
   274  	return func(c interface{}) {
   275  		c.(*config).rule = rule
   276  	}
   277  }
   278  
   279  // newLimiter 实例化限制器
   280  func newLimiter(options ...options.Option) overload.Limiter {
   281  	// 判断传入配置是否为空,否则使用默认配置
   282  	conf := defaultConf()
   283  	for _, opt := range options {
   284  		opt(conf)
   285  	}
   286  
   287  	size := conf.winBucket
   288  	bucketDuration := conf.window / time.Duration(conf.winBucket)
   289  	passStat := stat.NewRollingCounter(size, bucketDuration)
   290  	rtStat := stat.NewRollingCounter(size, bucketDuration)
   291  	cpu := func() int64 {
   292  		return atomic.LoadInt64(&cpu)
   293  	}
   294  	limiter := &BBR{
   295  		cpu:             cpu,
   296  		conf:            conf,
   297  		passStat:        passStat,
   298  		rtStat:          rtStat,
   299  		winBucketPerSec: int64(time.Second) / (int64(conf.window) / int64(conf.winBucket)),
   300  	}
   301  	return limiter
   302  }
   303  
   304  // NewGroup 实例化限制器容器
   305  func NewGroup(options ...options.Option) *Group {
   306  	// 判断传入配置是否为空,否则使用默认配置
   307  	_group := group.NewGroup(func() interface{} {
   308  		return newLimiter(options...)
   309  	})
   310  	return &Group{
   311  		group: _group,
   312  	}
   313  }
   314  
   315  // Get 通过指定的键获取一个限制器,如果不存在限制器,则重新创建一个限制器。
   316  func (g *Group) Get(key string) overload.Limiter {
   317  	limiter := g.group.Get(key)
   318  	return limiter.(overload.Limiter)
   319  }