Go 示例: 速率限制

速率限制 是一种重要的机制,用于控制资源利用率并维护服务质量。Go 通过 goroutine、通道和 计时器 优雅地支持速率限制。

package main
import (
    "fmt"
    "time"
)
func main() {

首先,我们将看一下基本的速率限制。假设我们想限制对传入请求的处理。我们将从同名通道中提供这些请求。

    requests := make(chan int, 5)
    for i := 1; i <= 5; i++ {
        requests <- i
    }
    close(requests)

limiter 通道将每 200 毫秒接收一个值。这是我们速率限制方案中的调节器。

    limiter := time.Tick(200 * time.Millisecond)

通过在为每个请求提供服务之前阻塞接收来自 limiter 通道的消息,我们将自己限制为每 200 毫秒 1 个请求。

    for req := range requests {
        <-limiter
        fmt.Println("request", req, time.Now())
    }

我们可能希望在速率限制方案中允许短时间内的请求突发,同时保留总体速率限制。我们可以通过缓冲 limiter 通道来实现这一点。此 burstyLimiter 通道将允许最多 3 个事件的突发。

    burstyLimiter := make(chan time.Time, 3)

填满通道以表示允许的突发。

    for i := 0; i < 3; i++ {
        burstyLimiter <- time.Now()
    }

每 200 毫秒,我们将尝试向 burstyLimiter 添加一个新值,直到其限制为 3。

    go func() {
        for t := range time.Tick(200 * time.Millisecond) {
            burstyLimiter <- t
        }
    }()

现在模拟 5 个传入请求。前 3 个请求将受益于 burstyLimiter 的突发功能。

    burstyRequests := make(chan int, 5)
    for i := 1; i <= 5; i++ {
        burstyRequests <- i
    }
    close(burstyRequests)
    for req := range burstyRequests {
        <-burstyLimiter
        fmt.Println("request", req, time.Now())
    }
}

运行我们的程序,我们看到第一批请求按预期每 ~200 毫秒处理一次。

$ go run rate-limiting.go
request 1 2012-10-19 00:38:18.687438 +0000 UTC
request 2 2012-10-19 00:38:18.887471 +0000 UTC
request 3 2012-10-19 00:38:19.087238 +0000 UTC
request 4 2012-10-19 00:38:19.287338 +0000 UTC
request 5 2012-10-19 00:38:19.487331 +0000 UTC

对于第二批请求,我们立即处理前 3 个请求,因为使用了可突发的速率限制,然后以 ~200 毫秒的延迟分别处理剩余的 2 个请求。

request 1 2012-10-19 00:38:20.487578 +0000 UTC
request 2 2012-10-19 00:38:20.487645 +0000 UTC
request 3 2012-10-19 00:38:20.487676 +0000 UTC
request 4 2012-10-19 00:38:20.687483 +0000 UTC
request 5 2012-10-19 00:38:20.887542 +0000 UTC

下一个示例:原子计数器.