zoukankan      html  css  js  c++  java
  • 令牌桶的概念和实践

    参考:
    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")
    }
    
  • 相关阅读:
    网络拓扑
    OSI 7层模型和TCP/IP 4层模型
    第一范式 第二范式 第三范式 BC范式 第四范式
    医院 信息科
    李纳斯•托瓦兹
    所谓绅士,就是做自己该做之事,而不是想做之事。
    活着
    开头词
    人际题目
    人际关系
  • 原文地址:https://www.cnblogs.com/chq3272991/p/15667188.html
Copyright © 2011-2022 走看看