无法抓住我的头周围去率包

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 requests/second 吗?

首先,您有一场数据竞赛。多个 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 ms sleep,那么所有都将被允许,输出将是 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 events/sec:

limiter := rate.NewLimiter(rate.Limit(5), 5)

不休眠地使用这个limiter,你也会得到5的输出。这是因为在不达到速率限制的情况下允许 5 个事件,如果没有睡眠,则不允许其余事件。