bucket token 令牌桶算法是网络流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一种算法。
图片来源于网络
首先我们有一个bucket,里面存放了n个token,每次当有一个网络请求时,我们发一个token,当这个请求结束时,我们再将其token收回放入bucket中。这样我们的请求数量就不会同时超过桶内的token的数量。
可能有人想,我们创建一个数组来维护这个token也可以实现吧。但是在go语言中,我们对于每一个请求都是在一个单独的协程中处理的,所以当有并发时,这个数组就不好维护了。当然,我们可以加锁,但是可想而知,这个系统的性能必然下降。而且在go语言中,我们提倡的是使用channel来处理并发,所以我们将这个算法使用channel来处理是很理想的。也就是通过共享通道而不是用共享内存来解决。
今天我们先实现一个总的连接数量的限制的简单实现:
package main
import "fmt"
type Limiter struct {
cont int
bucket chan int
}
func NewConnLimiter(cc int) *Limiter {
return &Limiter{
cont: cc,
bucket: make(chan int, cc), // buffer channel
}
}
func (cl *Limiter) GetToken(id int) bool {
if len(cl.bucket) >= cl.cont {
fmt.Println("超过限制")
return false
}
cl.bucket <- id
return true
}
func (cl *Limiter) ReleaseToken() {
c := <-cl.bucket
fmt.Println(c)
}
func main() {
c := NewConnLimiter(5)
r := c.GetToken(1)
fmt.Println("获取token结果:", r)
for i := 0; i < 5; i++ {
r := c.GetToken(i)
fmt.Println("获取token结果:", r)
}
c.ReleaseToken()
}
这个代码是非常简单的,目的就是,在有一个手游转让连接时,我们就给他一个token,当他结束时,在释放这个token。我们看一下上面代码的运行结果:
运行结果
我们可以看到,开始时我们设置桶的数量为5,也就是最多有5个请求可以连接,当有一个token被释放之后,又可以有新的连接了。但是现在这个代码有一个缺点,就是总的连接数是固定的,有时我们需要的是在一段时间内,请求数量不能超过最大的值,这是我们就需要设置一个timer,在这段时间内,我们来判断token的数量,而且超过时间我们自动释放token就可以了。这个代码不再这里演示了,希望感兴趣的同学回去手动试一下,学习语言最好的方法就是动手。我之前也说过,我这里写的都是简单的实现,如果大家项目中有用到,就需要随机应变,但是基本思路都是类似的。活学活用很主要。