英文:
Unable to grasp my head around go rate package
问题
我需要对API的请求进行速率限制,考虑使用原生的golang.org/x/time/rate
包来实现。为了对其API进行一些调试,并确保我的假设是正确的,我创建了以下测试,但显然我在这里漏掉了一些东西:
package main
import (
"github.com/stretchr/testify/require"
"golang.org/x/time/rate"
"sync"
"testing"
)
func TestLimiter(t *testing.T) {
limiter := rate.NewLimiter(rate.Limit(5),1)
wg := sync.WaitGroup{}
successful := 0
for i:=1; i<=10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
if limiter.Allow() {
successful++
}
}()
}
wg.Wait()
require.Equal(t, 5, successful)
// This test fails with
// Expected :5
// Actual :1
}
为什么会出现这种情况的解释是什么?难道速率限制器不应该允许每秒5个请求吗?
英文:
I need to rate limit the requests to an API and I'm considering using the native golang.org/x/time/rate
package for that purpose. In order to fiddling a little bit with its API and make sure that my assumptions are correct, I've created this tests, but it definitely seems that I'm missing something here:
package main
import (
"github.com/stretchr/testify/require"
"golang.org/x/time/rate"
"sync"
"testing"
)
func TestLimiter(t *testing.T) {
limiter := rate.NewLimiter(rate.Limit(5),1)
wg := sync.WaitGroup{}
successful := 0
for i:=1; i<=10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
if limiter.Allow() {
successful++
}
}()
}
wg.Wait()
require.Equal(t, 5, successful)
// This test fails with
// Expected :5
// Actual :1
}
What is the explanation for why this is? Shouldn't the rate limiter allow 5 requests/second?
答案1
得分: 4
首先,你有一个数据竞争。多个 goroutine 在没有同步的情况下写入 successful
:这是未定义的行为。
你可以使用 sync/atomic
包进行简单且安全的计数:
limiter := rate.NewLimiter(rate.Limit(5), 1)
wg := sync.WaitGroup{}
successful := int32(0)
for i := 1; i <= 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
if limiter.Allow() {
atomic.AddInt32(&successful, 1)
}
}()
}
wg.Wait()
fmt.Println(successful)
这将输出:
1
为什么?因为你允许每秒 5 个事件,即每 0.2 秒 1 个事件。启动 10 个 goroutine 并进行检查将花费少于 0.2 秒,因此只允许一个事件。
如果在循环中添加 200 毫秒的睡眠,那么所有事件都将被允许,并且输出将为 10
:
for i := 1; i <= 10; i++ {
time.Sleep(200 * time.Millisecond)
wg.Add(1)
go func() {
defer wg.Done()
if limiter.Allow() {
atomic.AddInt32(&successful, 1)
}
}()
}
如果添加 100 毫秒的睡眠,那么平均而言将允许其中一半的事件,并且输出将为 5
。
你可能想要的是允许突发 5 个事件,每秒 5 个事件:
limiter := rate.NewLimiter(rate.Limit(5), 5)
使用这个 limiter
,如果没有睡眠,你也会得到一个输出为 5
。这是因为在不触及速率限制的情况下允许 5 个事件,而没有睡眠,其余的事件将不被允许。
英文:
First, you have a data race. Multiple goroutines write successful
without synchronization: undefined behavior.
You may use the sync/atomic
package for an easy and safe counting:
limiter := rate.NewLimiter(rate.Limit(5), 1)
wg := sync.WaitGroup{}
successful := int32(0)
for i := 1; i <= 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
if limiter.Allow() {
atomic.AddInt32(&successful, 1)
}
}()
}
wg.Wait()
fmt.Println(successful)
This will output:
1
Why? Because you allow 5 events per second, that's 1 event per 0.2 seconds. Launching 10 goroutines and checking will take less than 0.2 seconds, so only one event will be allowed.
If you add 200 ms sleep in the loop, then all will be allowed and output will be 10
:
for i := 1; i <= 10; i++ {
time.Sleep(200 * time.Millisecond)
wg.Add(1)
go func() {
defer wg.Done()
if limiter.Allow() {
atomic.AddInt32(&successful, 1)
}
}()
}
If you add 100 ms sleep, then on average half of them will be allowed and output will be 5
.
What you want probably is allow a burst of 5 with 5 events/sec:
limiter := rate.NewLimiter(rate.Limit(5), 5)
Using this limiter
without sleep, you'll also get an output of 5
. That's because 5 events are allowed without hitting the rate limit, and without sleep the rest will not be allowed.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论