You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
naza/pkg/ratelimit/leakybucket.go

87 lines
2.3 KiB
Go

// 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
}