|
|
// Copyright 2020, Chef. All rights reserved.
|
|
|
// https://github.com/q191201771/naza
|
|
|
//
|
|
|
// Use of this source code is governed by a MIT-style license
|
|
|
// that can be found in the License file.
|
|
|
//
|
|
|
// Author: Chef (191201771@qq.com)
|
|
|
|
|
|
package ratelimit
|
|
|
|
|
|
import (
|
|
|
"errors"
|
|
|
"sync"
|
|
|
"time"
|
|
|
)
|
|
|
|
|
|
var ErrResourceNotAvailable = errors.New("naza.ratelimit: resource not available")
|
|
|
|
|
|
// 漏桶
|
|
|
type LeakyBucket struct {
|
|
|
intervalMs int64
|
|
|
|
|
|
mu sync.Mutex
|
|
|
lastTick int64
|
|
|
}
|
|
|
|
|
|
// @param intervalMs 多长时间以上,允许获取到一个资源,单位毫秒
|
|
|
func NewLeakyBucket(intervalMs int) *LeakyBucket {
|
|
|
return &LeakyBucket{
|
|
|
intervalMs: int64(intervalMs),
|
|
|
// 注意,第一次获取资源,需要与创建对象时的时间点做比较
|
|
|
lastTick: time.Now().UnixNano() / 1e6,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 尝试获取资源,获取成功返回nil,获取失败返回ErrResourceNotAvailable
|
|
|
// 如果获取失败,上层可自由选择多久后重试或丢弃本次任务
|
|
|
func (lb *LeakyBucket) TryAquire() error {
|
|
|
lb.mu.Lock()
|
|
|
defer lb.mu.Unlock()
|
|
|
nowMs := time.Now().UnixNano() / 1e6
|
|
|
|
|
|
// 距离上次获取成功时间超过了间隔阈值,返回成功
|
|
|
if nowMs-lb.lastTick > lb.intervalMs {
|
|
|
lb.lastTick = nowMs
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
return ErrResourceNotAvailable
|
|
|
}
|
|
|
|
|
|
// 阻塞直到获取到资源
|
|
|
func (lb *LeakyBucket) WaitUntilAquire() {
|
|
|
lb.mu.Lock()
|
|
|
nowMs := time.Now().UnixNano() / 1e6
|
|
|
|
|
|
diff := nowMs - lb.lastTick
|
|
|
if diff > lb.intervalMs {
|
|
|
lb.lastTick = nowMs
|
|
|
lb.mu.Unlock()
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// 没有达到间隔,我们更新lastTick再出锁,使得其他想获取资源的协程以新的lastTick作为判断条件
|
|
|
lb.lastTick += lb.intervalMs
|
|
|
lb.mu.Unlock()
|
|
|
|
|
|
// 我们不需要等整个interval间隔,因为可能已经过去了一段时间了,
|
|
|
// 注意,diff是根据更新前的lastTick计算得到的
|
|
|
time.Sleep(time.Duration(lb.intervalMs-diff) * time.Millisecond)
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// 最快可获取到资源距离当前的时长, 但是不保证获取时一定能抢到
|
|
|
// 返回0,说明可以获取,返回非0,则是对应的时长,单位毫秒
|
|
|
func (lb *LeakyBucket) MaybeAvailableIntervalMs() int64 {
|
|
|
lb.mu.Lock()
|
|
|
defer lb.mu.Unlock()
|
|
|
nowMs := time.Now().UnixNano() / 1e6
|
|
|
|
|
|
if nowMs-lb.lastTick > lb.intervalMs {
|
|
|
return 0
|
|
|
}
|
|
|
|
|
|
return lb.lastTick + lb.intervalMs - nowMs
|
|
|
}
|