限流器是提升服务稳定性的非常重要的组件,可以用来限制请求速率,保护服务,以免服务过载。限流器的实现方法有很多种,常见的限流算法有固定窗口、滑动窗口、漏桶、令牌桶

简单来说,令牌桶就是想象有一个固定大小的桶,系统会以恒定速率向桶中放 Token,桶满则暂时不放。在请求比较的少的时候桶可以先"攒"一些Token,应对突发的流量,如果桶中有剩余 Token 就可以一直取。如果没有剩余 Token,则需要等到桶中被放置了 Token 才行。

有的同学在看明白令牌桶的原理后就非常想去自己实现一个限流器应用到自己的项目里,em... 怎么说呢,造个轮子确实有利于自己水平提高,不过要是应用到商用项目里的话其实大可不必自己去造轮子,Golang官方已经替我们造好轮子啦 ......~!

golang.org/x/time/rate

限流器的内部结构

time/rateLimiterLimiter

其主要字段的作用是:

limitlimit
timer/rate
time/rate

构造限流器

我们可以使用以下方法构造一个限流器对象:

这里有两个参数:

r Limitlimitb intburst

那么,对于以上例子来说,其构造出的限流器的令牌桶大小为 100, 以每秒 10 个 Token 的速率向桶中放置 Token。

r Limit

以上就表示每 100ms 往桶中放一个 Token。本质上也是一秒钟往桶里放 10 个。

使用限流器

Limiter 提供了三类方法供程序消费 Token,可以每次消费一个 Token,也可以一次性消费多个 Token。每种方法代表了当 Token 不足时,各自不同的对应手段,可以阻塞等待桶中Token补充,也可以直接返回取Token失败。

Wait/WaitN

WaitN(ctx,1)

当使用 Wait 方法消费 Token 时,如果此时桶内 Token 数组不足 (小于 N),那么 Wait 方法将会阻塞一段时间,直至 Token 满足条件。如果充足则直接返回。

这里可以看到,Wait 方法有一个 context 参数。我们可以设置 context 的 Deadline 或者 Timeout,来决定此次 Wait 的最长时间。

Allow/AllowN

AllowN(time.Now(),1)

AllowN 方法表示,截止到某一时刻,目前桶中数目是否至少为 n 个,满足则返回 true,同时从桶中消费 n 个 token。反之不消费桶中的Token,返回false。

对应线上的使用场景是,如果请求速率超过限制,就直接丢弃超频后的请求。

Reserve/ReserveN

ReserveN(time.Now(), 1)
*ReservationDelay()time.DurationCancel()

举一个简单的例子,我们可以这么使用 Reserve 方法。

动态调整速率和桶大小

Limiter 支持创建后动态调整速率和桶大小:

  • SetLimit(Limit) 改变放入 Token 的速率

  • SetBurst(int) 改变 Token 桶大小

有了这两个方法,可以根据现有环境和条件以及我们的需求,动态地改变 Token 桶大小和速率。

总结

今天我们总结了 Golang 官方限流器的使用方法,它是一种令牌桶算实现的限流器。其中 Wait/WaitNAllow/AllowN 这两组方法在平时用的比较多,前者是消费Token时如果桶中Token不足可以让程序等待桶中新Token的放入(最好设置上等待时长)后者则是在桶中的Token不足时选择直接丢弃请求。

uber-go/ratelimit