Loading

令牌桶的概念和实践

参考:
https://www.freeaihub.com/post/105431.html
http://hustcat.github.io/rate-limit-example-in-go/

令牌桶模型

官方实现:golang.org/x/time/rate

主要方法为:

type Limiter struct  // 结构体定义
func NewLimiter(r Limit, b int) *Limiter  // 初始化
func (lim *Limiter) Limit() Limit
func (lim *Limiter) Burst() int
func (lim *Limiter) AllowN(now time.Time, n int) bool  
func (lim *Limiter) ReserveN(now time.Time, n int) *Reservation
func (lim *Limiter) WaitN(ctx context.Context, n int) (err error)

func (lim *Limiter) SetBurst(newBurst int)
func (lim *Limiter) SetBurstAt(now time.Time, newBurst int)
func (lim *Limiter) SetLimit(newLimit Limit)
func (lim *Limiter) SetLimitAt(now time.Time, newLimit Limit)

其中AllowN、ReserveN、WaitN分别可以简化为Allow、Reserve、Wait,此时n为1

初始化NewLimiter

r Limit为float64类型,表示每秒产生token的速度,b表示桶的大小

AllowN(严格QPS控制, 熔断降级)

AllowN(now,n)表示截止到now这个时间点,是否存在n个token,如果存在则返回true,反正返回false。一般对于限流比较严格,判断false时,直接忽略当前请求可以用该方法

ReserveN(文件下载上传限流控制,获取等待时间,结合sleep使用)

ReserveN(now,n)表示截止到now这个时间点,是否存在n个token,区别AllowN返回一个Reservation对象,对象中包含几个方法:

func (r *Reservation) OK() bool  
func (r *Reservation) Delay() time.Duration
func (r *Reservation) DelayFrom(now time.Time) time.Duration
func (r *Reservation) Cancel()
func (r *Reservation) CancelAt(now time.Time)
  • 调用OK()可以知道是否通过等待可以获取到n个token(这个是指n如果大于令牌桶初始化的b容量,则永远获取不到n个token,ok必定返回false)
  • 调用Delay()可以指定需要等待的时间
  • 调用Cancel表示不想等待了,直接返还token

WaitN(QPS控制,阻塞等待)

WaitN(ctx,n)表示是否存在n个token,如果存在则直接转发,不存在就阻塞等待到存在为止,传入ctx的Deadline就是等待的最长Deadline,超时则结束等待

动态流量控制

通过调用SetBurst和SetLimit可以动态的设置桶的大小和token产生速率,其中SetBurstAt和SetLimitAt会将传入的时间now设置为流控最后的更新时间

基于ip的gin限流中间件

使用sync.map来为每一个ip创建一个limiter,ip作为key也可以换成其他类似客户端cookie、user_name等等信息

// r 每秒token产生的速率
// b 最大突发量
// t 等待超时时间
func NewLimiter(r rate.Limit,b int,t time.Duration) (gin.HandlerFunc) {
	limiters := &sync.Map{}
	
	return func(c *gin.Context) {
		// 获取限速器
		key := c.ClientIP()
		l, _ := limiters.LoadOrStore(key,rate.NewLimiter(r,b))

		// 这里主要不要直接使用gin的context默认是没有超时时间的
		ctx, cancel := context.WithTimeout(c,t)
		defer cancel()

		if err := l.(*rate.Limiter).Wait(ctx); err!= nil {
			// 这里表示超过最大突发量
			// 直接返回错误,也可以补充其他日志写入等操作
			c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{"error":err})
		}
	}
}

使用的时候,gin调用use处理中间件

func main() {
	e := gin.Default()
	// 新建一个限速器,允许突发 10 个并发,限速 3rps,超过 500ms 就不再等待
	e.Use(NewLimiter(3, 10, 500*time.Millisecond))
	e.GET("ping", func(c *gin.Context) {
		c.String(http.StatusOK, "pong")
	})
	e.Run(":8080")
}
posted @ 2021-12-09 14:32  集君  阅读(156)  评论(0编辑  收藏  举报