前言
一些函数的执行可能会限制频率,比如某个api接口要求每秒最大请求30次。下面记录了自己写的限频和官方的限频
代码
// 加锁限频,输出次数大概率小于最大值
func ExecLimit(lastExecTime *time.Time, l *sync.RWMutex ,maxTimes int, perDuration time.Duration, f func()) {
l.Lock()
defer l.Unlock()
// per times cost time(s)
SecondsPerTimes := float64(perDuration) / float64(time.Second) / float64(maxTimes)
now := time.Now()
interval := now.Sub(*lastExecTime).Seconds()
if interval < SecondsPerTimes {
time.Sleep(time.Duration(int64((SecondsPerTimes-interval)*1000000000)) * time.Nanosecond)
}
f()
*lastExecTime = time.Now()
}
// 官方的,需要引用 "golang.org/x/time/rate"
// 基本上可以达到满值,比自己写的更优
func ExecLimit2(l *rate.Limiter, f func()) {
go func() {
l.Wait(context.Background())
f()
}()
}
使用
func TestExecLimit(t *testing.T) {
runtime.GOMAXPROCS(runtime.NumCPU())
go func() {
var lastExecTime time.Time
var l sync.RWMutex
for {
ExecLimit(&lastExecTime, &l, 10, time.Second, func() {
fmt.Println("do")
})
}
}()
select {
case <-time.After(1 * time.Second):
fmt.Println("1秒到时")
}
}
func TestExecLimit2(t *testing.T) {
runtime.GOMAXPROCS(runtime.NumCPU())
l := rate.NewLimiter(1, 30)
go func() {
for {
ExecLimit2(l, func() {
fmt.Println("do")
})
}
}()
select {
case <-time.After(1 * time.Second):
fmt.Println("1秒到时")
}
}
输出:
一秒内输出了<=10次 "do"
如何在多节点服务中限制频
上述使用,定义在某个服务节点的全局变量lastExecTime仅仅会对该服务的函数f()操作限频,如果在负载均衡后,多个相同服务的节点,对第三方的接口累计限频,比如三个服务共同拉取第三方接口,合计限频为30次/s.
则,必须将lastExecTime的获取,从redis等共享中间件中获取,而不应该从任何一个单点服务获取。