限流算法

计数器

在固定时间间隔内,处理请求有上限,请求超出部分丢弃。

package main

import (
	"sync"
	"time"

	klog "k8s.io/klog/v2"
)

type counterRateLimiter struct {
	m             sync.Mutex
	startPartTime int64
	endPartTime   int64
	maxCount      int
	currCount     int
}

func (c *counterRateLimiter) isLimit() bool {
	c.m.Lock()
	defer c.m.Unlock()

	currentTime := time.Now().UnixMilli()
	// 区间考虑前闭后开
	if currentTime >= c.endPartTime {
		c.startPartTime += (currentTime - c.startPartTime) / 1000 * 1000
		c.endPartTime = c.startPartTime + 1000
		c.currCount = 0
	}

	if c.currCount < c.maxCount {
		c.currCount++
		return true
	}
	return false
}

func main() {
	crl := counterRateLimiter{
		m:             sync.Mutex{},
		startPartTime: time.Now().UnixMilli(),
		endPartTime:   time.Now().UnixMilli() + 1000,
        // 1s处理5个请求
		maxCount:      5,
		currCount:     0,
	}

	for i := 0; i < 6; i++ {
		klog.Infof("isLimit is %v", crl.isLimit())
	}
	time.Sleep(500 * time.Millisecond)
	for i := 0; i < 6; i++ {
		klog.Infof("isLimit is %v", crl.isLimit())
	}
	time.Sleep(time.Second)
	for i := 0; i < 6; i++ {
		klog.Infof("isLimit is %v", crl.isLimit())
	}
}

在单秒内没有请求时,可以应对突发流量。

漏桶

漏桶(装请求)容量固定,请求下发速率固定,请求超出漏桶容量部分丢弃。

package main

import (
	"sync"
	"time"

	klog "k8s.io/klog/v2"
)

type waterBucketRateLimiter struct {
	m             sync.Mutex
	interval      int
	bucketCap     int
	countInBucket int
	ch            chan int
}

func initWaterBucketRateLimiter(maxCount, bucketCap int) *waterBucketRateLimiter {
	klog.Infof("init water bucket rate limiter")
	w := &waterBucketRateLimiter{
		m:             sync.Mutex{},
		interval:      1000 / maxCount,
		bucketCap:     bucketCap,
		countInBucket: 0,
		ch:            make(chan int, maxCount),
	}

	go w.handle()
	return w
}

// 新增请求
func (w *waterBucketRateLimiter) addWater() bool {
	w.m.Lock()
	defer w.m.Unlock()

	if w.countInBucket < w.bucketCap {
		w.countInBucket++
		return true
	}
	return false
}

func (w *waterBucketRateLimiter) handle() {
	t := time.NewTicker(time.Duration(w.interval) * time.Millisecond)
	defer t.Stop()

	for {
		select {
		case <-t.C:
			w.m.Lock()

			if w.countInBucket == 0 {
				continue
			}
			w.countInBucket--
			w.ch <- 1

			w.m.Unlock()
		}
	}
}

func main() {
	wbrl := initWaterBucketRateLimiter(5, 10)

	for i := 0; i < 10; i++ {
		wbrl.addWater()
	}

	for _ = range wbrl.ch {
		klog.Info("get item from rate limiter")
	}
}

固定间隔200ms/个下发请求,平滑处理流量。

令牌桶

令牌桶(装令牌,不装请求)容量固定,令牌产生速度固定,取到令牌后处理请求,消耗令牌速度不固定,请求没有取到令牌丢弃。

package main

import (
	"sync"
	"time"

	klog "k8s.io/klog/v2"
)

type tokenBucketRateLimiter struct {
	m                  sync.Mutex
	interval           int
	bucketCap          int
	tokenCountInBucket int
}

func initTokenBucketRateLimiter(maxCount, bucketCap int, wg *sync.WaitGroup, stopCh chan bool) *tokenBucketRateLimiter {
	w := &tokenBucketRateLimiter{
		m:                  sync.Mutex{},
		interval:           1000 / maxCount,
		bucketCap:          bucketCap,
		tokenCountInBucket: bucketCap,
	}

	go w.putTokenIntoBucket(wg, stopCh)
	return w
}

func (tbrl *tokenBucketRateLimiter) putTokenIntoBucket(wg *sync.WaitGroup, stopCh chan bool) {
	t := time.NewTicker(time.Duration(tbrl.interval) * time.Millisecond)
	defer t.Stop()

	wg.Add(1)

	for {
		select {
		case <-t.C:
			tbrl.m.Lock()
			if tbrl.tokenCountInBucket < tbrl.bucketCap {
				tbrl.tokenCountInBucket++
			}
			tbrl.m.Unlock()
		case <-stopCh:
			wg.Done()
			return
		}
	}
}

func (tbrl *tokenBucketRateLimiter) isLimit() bool {
	tbrl.m.Lock()
	defer tbrl.m.Unlock()

	if tbrl.tokenCountInBucket > 0 {
		tbrl.tokenCountInBucket--
		return true
	}
	return false
}

func main() {
	wg := sync.WaitGroup{}
	stopCh := make(chan bool)
	tbrl := initTokenBucketRateLimiter(5, 10, &wg, stopCh)

	for i := 0; i < 20; i++ {
		klog.Infof("get token result is %v", tbrl.isLimit())
	}
	time.Sleep(time.Second)
	for i := 0; i < 6; i++ {
		klog.Infof("get token result is %v", tbrl.isLimit())
	}
	stopCh <- true

	wg.Wait()
}

与漏桶相比,令牌桶不是固定间隔下发请求,可能因没有请求且令牌桶容量大,令牌桶中存了大量令牌,应对突发流量时可以快速下发一部分。

posted on 2023-12-30 20:52  王景迁  阅读(6)  评论(0编辑  收藏  举报

导航