time/rate是基于Token Bucket(令牌桶)算法实现的限流

限流

限制某个服务每秒的调用本服务的频率 客户端请求太多,超出服务端的服务能力,导致服务不可用。DoS攻击就是根据此原理, 耗尽被攻击对象的资源,让目标系统无法响应甚至崩溃。解决方案:服务端对客户端限流,保护服务端的资源。 限流通常在网关或网络层面实施。对各类请求设置最高的QPS阈值,当请求高于阈值时直接阻断。

常用的限流算法有滑动计数,漏斗限流和令牌限流三种:

  1. 滑动计数限流:按时间片(比如1秒)定义滑动窗口,计数器记录当前窗口的请求次数, 达到阈值就限流,窗口滑动后计数器归零。可采用循环队列数据结构实现。
  2. 漏斗限流:维护一个队列,所有请求进队列,按FIFO服务,队满溢出则丢弃请求。
  3. 令牌桶限流:按固定速率往桶中存入令牌,服务前先从桶中取令牌,取到令牌才服务。

Golang官方限流器使用

安装

1
go get golang.org/x/time/rate

构造一个限流器

1
2
3
4
5
6
7
8
// NewLimiter returns a new Limiter that allows events up to rate r and permits
// bursts of at most b tokens.
func NewLimiter(r Limit, b int) *Limiter {
    return &Limiter{
        limit: r,
        burst: b,
    }
}

NewLimiter有两个参数

第一个r Limit 表示每秒可以放入多少个token到桶中,Limit是float64的别名;

第二个b int 表示桶容量大小,即同一时刻能取到的最大token数量;

1
limiter := NewLimiter(10, 1);

示例表示每秒放入10个token,桶容量大小为1

1
2
limit := Every(100 * time.Millisecond);
limiter := NewLimiter(limit, 1);

Every表示放入token速率时间粒度;

示例表示每100ms放入1个token,即1秒放入10个;

Wait/WaitN

1
2
func (lim *Limiter) Wait(ctx context.Context) (err error)
func (lim *Limiter) WaitN(ctx context.Context, n int) (err error)

Wait获取Token时如果数组不足(小于N),将会阻塞一段时间,直至Token满足条件, 如果充足则直接返回

阻塞时间可以通过context参数设置Deadline或Timeout控制

Allow/AllowN

1
2
func (lim *Limiter) Allow() bool
func (lim *Limiter) AllowN(now time.Time, n int) bool

Allow获取Token充足返回true,同时Token减少,否则返回false,不会阻塞

Reserve/ReserveN

1
2
func (lim *Limiter) Reserve() *Reservation
func (lim *Limiter) ReserveN(now time.Time, n int) *Reservation

返回Reservation对象,有如下对象方法:

1
2
3
4
5
6
7
func (r *Reservation) OK() bool // 判断是否获取到token
// Delay is shorthand for DelayFrom(time.Now()).
func (r *Reservation) Delay() time.Duration // 获取延迟等待时间,此时Cancel不起作用
func (r *Reservation) DelayFrom(now time.Time) time.Duration
// Cancel is shorthand for CancelAt(time.Now()).
func (r *Reservation) Cancel() // 取消,将获取的Token重新放入桶中
func (r *Reservation) CancelAt(now time.Time)

调整速率和桶大小

1
2
3
4
5
func (lim *Limiter) SetLimit(newLimit Limit) //改变放入Token的速率
func (lim *Limiter) SetLimitAt(now time.Time, newLimit Limit)

func (lim *Limiter) SetBurst(newBurst int) // 改变Token桶大小
func (lim *Limiter) SetBurstAt(now time.Time, newBurst int)

获取速率和桶大小

1
2
func (lim *Limiter) Limit() Limit // 获取速率
func (lim *Limiter) Burst() int //获取桶容量

参考