【问题标题】:Unable to grasp my head around go rate package无法抓住我的头围棋率包
【发布时间】:2021-12-20 07:08:38
【问题描述】:

我需要对 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 req/s 吗?

【问题讨论】:

    标签: go rate-limiting rate ratelimit


    【解决方案1】:

    首先,您有一场数据竞赛。多个 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 个突发事件/秒:

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

    在没有睡眠的情况下使用这个limiter,您还将获得5 的输出。这是因为允许 5 个事件而不达到速率限制,而没有睡眠则不允许其余的事件。

    【讨论】:

    • 非常感谢您的详细回答@icza!!现在对我来说很清楚:-)
    • 我对爆裂还有一个疑问。假设我设置了 3 个 limiter := rate.NewLimiter(rate.Limit(5), 3) 的突发,并将 time.Sleep(100 * time.Millisecond) 放在条件 if i &gt; 3 { 中。那么输出应该是什么?我认为它应该同样是5,因为由于突发参数,应该允许前三个连续请求,然后它应该像往常一样对其余请求进行速率限制,直到达到 5 的限制。但是,输出 I我得到的是6
    • @beni0888 我得到7,我认为这很好。您有一个包含 3 个令牌的存储桶,每 100 毫秒尝试取出 1 个,然后每 200 毫秒添加一个。
    • 这真的很奇怪,因为我不断收到6... 无论如何,我可能要求的太多了,但如果你能尝试解释一下爆发的概念实际上会很好有效,因为这对我来说似乎很混乱,我认为它没有很好的记录
    • @beni0888 rate.Limiter 实现了一个令牌桶。文档链接到en.wikipedia.org/wiki/Token_bucket,它更详细地描述了这一点。也正如我之前的评论中提到的:把它想象成一个有 3 个令牌的桶,每 0.2 秒添加一个新令牌(如果未满),每 0.1 秒取出一个(如果有的话)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-05
    • 2018-07-31
    • 1970-01-01
    • 1970-01-01
    • 2014-04-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多