参考:
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")
}