限流对某一时间窗口内高于系统承载的请求进行限制,通过限速来保护系统,一旦达到限制速率则可拒绝服务,等待。常见调用平台及服务,比如微信发消费券服务每秒500qps,万一我们超过请求频次,就会发生意想不到的业务问题,踩过坑的小伙伴深有体会
常见限流方法:计数器、令牌桶、漏桶。这里我们就只展开对令牌桶展开讨论。令牌桶是以一定的速率往令牌桶中生产令牌,桶满则丢弃,请求request过来时,先从桶中获取一个令牌,成功获取令牌就处理请求,失败就丢弃请求
令牌桶实现原理
juju/ratelimit令牌桶限流器在golang开发中使用比较多,而且自己在项目中刚好需要使用到、今天就从它进行展开了解,学习使用和实现的原理
juju/ratelimit中主要有三个文件,ratelimit.go, ratelimit_test.go、reader.go
先看ratelimit定义令牌桶的结构Bucket,简单看Bucket中属性的值代表的意义
type Bucket struct {
//Clock是个接口,由realClock根据标准时间函数实现
clock Clock
//令牌桶首次创建的时间
startTime time.Time
//桶容量
capacity int64
//每个tick向桶内添加的令牌数
quantum int64
//每个刻度的间隔
fillInterval time.Duration
//锁
mu sync.Mutex
//相关的tick有效令牌数,消费者等待令牌时为负数
availableTokens int64
//令牌中的数量保持最新的刻度
latestTick int64
}
整个ratelimit对于ticket使用和更新贯穿整个令牌桶流程,无论从注释和代码中都有tick的使用:tick = (当前时间 - 初始时间)/ 时间间隔(fillInterval )
func (tb *Bucket) currentTick(now time.Time) int64 {
return int64(now.Sub(tb.startTime) / tb.fillInterval)
}
// 指定时间间隔、容量大小的令牌桶、每个tick添加的令牌数、 clock的类型是Clock
func NewBucket(fillInterval time.Duration, capacity int64) *Bucket
// 指定时间间隔、容量大小、每个tick添加的令牌数 quantum
func NewBucketWithQuantum(fillInterval time.Duration, capacity, quantum int64) *Bucket
// 创建填充速度为指定速率和容量大小的令牌桶
// NewBucketWithRate(0.5, 100) 表示每秒填充50个令牌, fillInterval 时间间隔为1
func NewBucketWithRate(rate float64, capacity int64) *Bucket
其实这三种方式调用的都是下面函数的二次封装
func NewBucketWithQuantumAndClock(fillInterval time.Duration, capacity, quantum int64, clock Clock) *Bucket
下面到了获取令牌的时候了TakeAvailable() 获取令牌,获取令牌会使用sync.Mutx 进行加锁,执行完后解锁。
获取令牌时,调整令牌桶的的代码如下
func (tb *Bucket) adjustavailableTokens(tick int64) {
if tb.availableTokens >= tb.capacity {
return
}
tb.availableTokens += (tick - tb.latestTick) * tb.quantum
if tb.availableTokens > tb.capacity {
tb.availableTokens = tb.capacity
}
tb.latestTick = tick
return
}
上面更新令牌数的方式没有通过其他的goroutine等方式去更新,而是转换为了挺有意思的数学问题,简单明了直接。当然juju/ratelimit还有其他一些获取令牌桶信息的方法,这里就不展开讨论了。具体我们可以去看源码,整体代码量还是可以接受的,哈哈