1
0
mirror of https://github.com/chai2010/advanced-go-programming-book.git synced 2025-05-24 20:52:22 +00:00

update rl

This commit is contained in:
Xargin 2018-06-16 18:26:48 +08:00
parent 49caab8ace
commit 5b8c49f3b9

View File

@ -108,6 +108,8 @@ Transfer/sec: 5.51MB
这两种方法看起来很像,不过还是有区别的。漏桶流出的速率固定,而令牌桶只要在桶中有令牌,那就可以拿。也就是说令牌桶是允许一定程度的并发的,比如同一个时刻,有 100 个用户请求,只要令牌桶中有 100 个令牌,那么这 100 个请求全都会放过去。令牌桶在桶中没有令牌的情况下也会退化为漏桶模型。
TODO这里需要画漏桶和令牌桶的图
实际应用中令牌桶应用较为广泛,开源界流行的限流器大多数都是基于令牌桶思想的。并且在此基础上进行了一定程度的扩充,比如 `github.com/juju/ratelimit` 提供了几种不同特色的令牌桶填充方式:
```go
@ -142,6 +144,108 @@ func (tb *Bucket) WaitMaxDuration(count int64, maxWait time.Duration) bool {}
## 原理
从功能上来看,令牌桶模型实际上就是对全局计数的加减法操作过程,但使用计数需要我们自己加读写锁,有小小的思想负担。如果我们对 Go 语言已经比较熟悉的话,很容易想到可以用 buffered channel 来完成简单的加令牌取令牌操作:
```go
var tokenBucket = make(chan struct{}, capacity)
```
每过一段时间向 tokenBucket 中添加 token如果 bucket 已经满了,那么直接放弃:
```go
fillToken := func() {
ticker := time.NewTicker(fillInterval)
for {
select {
case <-ticker.C:
select {
case tokenBucket <- struct{}{}:
default:
}
fmt.Println("current token cnt:", len(tokenBucket), time.Now())
}
}
}
```
把代码组合起来:
```go
package main
import (
"fmt"
"time"
)
func main() {
var fillInterval = time.Millisecond * 10
var capacity = 100
var tokenBucket = make(chan struct{}, capacity)
fillToken := func() {
ticker := time.NewTicker(fillInterval)
for {
select {
case <-ticker.C:
select {
case tokenBucket <- struct{}{}:
default:
}
fmt.Println("current token cnt:", len(tokenBucket), time.Now())
}
}
}
go fillToken()
time.Sleep(time.Hour)
}
```
看看运行结果:
```shell
current token cnt: 98 2018-06-16 18:17:50.234556981 +0800 CST m=+0.981524018
current token cnt: 99 2018-06-16 18:17:50.243575354 +0800 CST m=+0.990542391
current token cnt: 100 2018-06-16 18:17:50.254628067 +0800 CST m=+1.001595104
current token cnt: 100 2018-06-16 18:17:50.264537143 +0800 CST m=+1.011504180
current token cnt: 100 2018-06-16 18:17:50.273613018 +0800 CST m=+1.020580055
current token cnt: 100 2018-06-16 18:17:50.2844406 +0800 CST m=+1.031407637
current token cnt: 100 2018-06-16 18:17:50.294528695 +0800 CST m=+1.041495732
current token cnt: 100 2018-06-16 18:17:50.304550145 +0800 CST m=+1.051517182
current token cnt: 100 2018-06-16 18:17:50.313970334 +0800 CST m=+1.060937371
```
在 1s 钟的时候刚好填满 100 个没有太大的偏差。不过这里可以看到Go 的定时器存在大约 0.001s 的误差,所以如果令牌桶大小在 1000 以上的填充可能会有一定的误差。对于一般的服务来说,这一点误差无关紧要。
上面的令牌桶的取令牌操作实现起来也比较简单,简化问题,我们这里只取一个令牌:
```go
func TakeAvailable(block bool) bool{
var takenResult bool
if block {
select {
case <-tokenBucket:
takenResult = true
}
} else {
select {
case <-tokenBucket:
takenResult = true
default:
takenResult = false
}
}
return takenResult
}
```
一些公司自己造的限流的轮子就是用上面这种方式来实现的,不过如果开源版 ratelimit 也如此的话,那我们也没什么可说的了。现实并不是这样的。
TODO
## 题外话: 分布式流量限制
TODO在线系统应该保证 CPU 资源的余量,以确保 QoS不应完全把资源用暴。