限流就是限制系统的输入和输出流量来达到保护系统的目的,限流在实际场景中应用十分广泛,尤其在高并发场景下,为了保证系统的可以用性,我们需要采取一些限流措施降级,一旦达到限制的阈值,就需要限制流量并采取一些措施来完成限制流量的目的(比如:延迟处理、拒绝处理等),以防止过多的请求而导致系统崩溃。
在golang的标准库golang.org/x/time/rate有一个限流器的实现,这个限流器的实现方案是令牌桶。
1、令牌桶
令牌桶是比较常见的限流算法之一,如下图所示:
令牌桶可以用来限制突发的流量,在下面图中,有一个桶,桶的大小是固定的,系统以一定的速率往桶中添加令牌,当桶满时,就会溢出,新添加的令牌会被丢弃。当请求需要被处理时,需要先从桶中获取令牌,当没有令牌可取时,则可以选择排队等待或者拒绝服务。

从上面的图看来,令牌桶的实现需要一个定时器和等待队列,定时器以一定的频率往桶中放入令牌,而等待队列用于存放等待的请求。但是这样的实现效率太低,在golang的标准库中的实现是通过计算时间的差值来算出令牌的。
2、标准库限流器的使用
标准库中的限流器相关定义如下:
Limit:速率,定义了某些事件的最大速率,为每秒事件数,也就是每秒往令牌桶中放入多少个令牌。Inf是无限速率。
type Limit float64
const Inf = Limit(math.MaxFloat64)
Every:这个函数可以将产生一个令牌的时间转化为每秒产生多少个令牌,比如100ms产生一个令牌,那么1s将产生10个令牌。
func Every(interval time.Duration) Limit {
if interval <= 0 {
return Inf
}
return 1 / Limit(interval.Seconds())
}
NewLimiter:创建一个限流器
func NewLimiter(r Limit, b int) *Limiter {
return &Limiter{
limit: r,
burst: b,
}
}
参数如下:
- 第一个参数r Limit:产生令牌的速率,也就是每秒往桶中放入多少个令牌。
- 第二个参数b int:令牌桶的大小。
对于下面这个例子,就是构造一个每秒产生10个令牌,令牌桶大小为20的限流器:
limiter := NewLimiter(10, 20)
2.1 消费令牌
Limiter提供了三种消费令牌的方法,可以用来消费一个或多个令牌,每种方法代表了当令牌不足时,各种的对应手段:
Wait / WaitNAllow / AllowNReserve / ReserveN
Wait / WaitN
func (lim *Limiter) Wait(ctx context.Context) (err error)
func (lim *Limiter) WaitN(ctx context.Context, n int) (err error)
Wait相当于WaitN(ctx, 1)
WaitN消费n个令牌,如果令牌的数量不够,将会阻塞等待。它的第一个参数为Context,也就是我们可以控制等待的最大时长。如果n超过了突发大小也就是桶的大小、Context被取消或者预期的等待时间超过了Context的Deadline,将会返回一个错误。
Allow / AllowN
func (lim *Limiter) Allow() bool
func (lim *Limiter) AllowN(now time.Time, n int) bool
Allow相当于AllowN(time.Now(), 1)
AllowN可以用来获取截至到某一时间,是否有n个令牌可用。如果满足则返回true,同时消费n个令牌,反之则不消费,返回false。
通常用于不满足条件则直接丢弃请求。
Reserve / ReserveN
func (lim *Limiter) Reserve() *Reservation
func (lim *Limiter) ReserveN(now time.Time, n int) *Reservation
Reserve相当于ReserveN(time.Now(), 1)
ReserveN返回一个Reservation,Reservation可用用来指示在有n个令牌可用之前必须等待多长时间。
可用调用Reservation的Delay方法来获取需要等待的时间:
func (r *Reservation) Delay() time.Duration
或者调用Cancel来取消,该方法会将token归还:
func (r *Reservation) Cancel()
ReserveN的使用示例如下:
r := lim.ReserveN(time.Now(), 1)
if !r.OK()


6928

被折叠的 条评论
为什么被折叠?



