我不熟悉速率限制,我想使用tollbooth来限制HTTP请求.

我还读了维基百科上Token Bucket 算法rithm页的内容.

对于一个简单的测试应用程序,我希望将并发请求的最大数量限制为10,而不考虑请求IP,并基于请求IP将最大突发大小限制为3.

注:103只是为了使速率限制更易于观察.

以下是我基于tollboothGitHub page的例子编写的代码:

package main

import (
    "net/http"
    "time"

    "github.com/didip/tollbooth/v7"
    "github.com/didip/tollbooth/v7/limiter"
)

func main() {
    lmt := tollbooth.NewLimiter(3, &limiter.ExpirableOptions{DefaultExpirationTTL: time.Hour})

    http.Handle("/", tollbooth.LimitFuncHandler(lmt, HelloHandler))
    http.ListenAndServe(":8080", nil)
}

func HelloHandler(w http.ResponseWriter, req *http.Request) {
    w.Write([]byte("Hello, World!"))
}

我通过快速连续运行curl -i localhost:8080几次来测试代码,每当我超过设置的速率限制时,我都会收到HTTP/1.1 429 Too Many Requests个错误.

以下是我的问题:

  1. 如何使用tollbooth将并发请求的最大数量限制在10左右?这样做有意义吗?我认为是这样的,因为仅基于IP的速率限制听起来像是当太多IP同时访问服务器时,服务器可能仍然会耗尽内存.

  2. 我是否正确地达到了速率限制,或者我错过了什么?也许这是更好地由任何负载均衡器在云中使用应用程序来处理的事情?

根据Woody1193的答案,我的工作代码如下:

package main

import (
    "net/http"
    "sync"
    "time"

    "github.com/didip/tollbooth/v7"
    "github.com/didip/tollbooth/v7/limiter"
)

func main() {
    ipLimiter := tollbooth.NewLimiter(3, &limiter.ExpirableOptions{DefaultExpirationTTL: time.Hour})
    globalLimiter := NewConcurrentLimiter(10)

    http.Handle("/", globalLimiter.LimitConcurrentRequests(ipLimiter, HelloHandler))
    http.ListenAndServe(":8080", nil)
}

func HelloHandler(w http.ResponseWriter, req *http.Request) {
    w.Write([]byte("Hello, World!"))
}

type ConcurrentLimiter struct {
    max     int
    current int
    mut     sync.Mutex
}

func NewConcurrentLimiter(limit int) *ConcurrentLimiter {
    return &ConcurrentLimiter{
        max: limit,
    }
}

func (limiter *ConcurrentLimiter) LimitConcurrentRequests(lmt *limiter.Limiter,
    handler func(http.ResponseWriter, *http.Request)) http.Handler {

    middle := func(w http.ResponseWriter, r *http.Request) {

        limiter.mut.Lock()
        maxHit := limiter.current == limiter.max

        if maxHit {
            limiter.mut.Unlock()
            http.Error(w, http.StatusText(429), http.StatusTooManyRequests)
            return
        }

        limiter.current += 1
        limiter.mut.Unlock()

        defer func() {
            limiter.mut.Lock()
            limiter.current -= 1
            limiter.mut.Unlock()
        }()

        // There's no rate-limit error, serve the next handler.
        handler(w, r)
    }

    return tollbooth.LimitHandler(lmt, http.HandlerFunc(middle))
}

推荐答案

看起来收费站没有提供你想要的功能.但是,您可以使用您自己的版本:

type ConcurrentLimiter struct {
    max int
    current int
    mut sync.Mutex
}

func NewConcurrentLimiter(limit int) *ConcurrentLimiter {
    return &ConcurrentLimiter {
        max: limit,
        mut: new(sync.Mutex),
    }
}

func (limiter *ConcurrentLimiter) LimitConcurrentRequests(lmt *limiter.Limiter, 
    next http.Handler) http.Handler {

    middle := func(w http.ResponseWriter, r *http.Request) {

        limiter.mut.Lock()
        maxHit := limiter.current == limiter.max
        if maxHit {
            limiter.mut.Unlock()
            httpError := // Insert your HTTP error here
            return
        }

        limiter.current += 1
        limiter.mut.Unlock()

        defer func() {
            limiter.mut.Lock()
            limiter.current -= 1
            limiter.mut.Unlock()
        }()

        // There's no rate-limit error, serve the next handler.
        next.ServeHTTP(w, r)
    }

    return tollbooth.LimitHandler(lmt, http.HandlerFunc(middle))
}

然后,在您的设置中可以执行以下操作:

http.Handle("/", NewConcurrentLimiter(10).LimitConcurrentRequests(HelloHandler))

此代码的工作方式是维护一个值,该值描述API当前正在处理的请求数,如果达到最大值,则返回错误.Mutex用于确保无论并发请求如何,都会更新该值.

我不得不将tollbooth.Limiter注入我编写的限制器中,因为Toll Both处理这些功能的方式(即它不是作为中间件运行的).

Go相关问答推荐

将Go程序导出到WASM—构建约束排除所有Go文件

切换选项卡时,Goland IDE中的光标自动转移

Golang内置打印(Ln)函数&S行为怪异

go-jwt 令牌验证错误 - 令牌签名无效:密钥类型无效

如何从 Go Lambda 函数返回 HTML?

如何将 DirName 和 serial 添加到 X509v3 Authority Key Identifier

如何以干净的方式在中间件中注入 repo 或服务?

GoLang: gocui 边框 colored颜色

为什么互斥量比 golang 中的通道慢?

接受通道和切片的通用函数

Golang - 将 [8] 布尔转换为字节

GRPC 反向代理混淆 GRPC 和 GRPC-Web

显示作为服务帐户身份验证的谷歌日历事件 - Golang App

函数参数的判断顺序是什么?

Go:为一组单个结果实现 ManyDecode

在 go (1.18) 的泛型上实现多态的最佳方法是什么?

是否存在一个Go泛型类型约束,该约束捕获了将类型用作映射中的键的能力?

我应该明确地创建一个与Belongs To或Has Many对称的关系吗?

带有 *s3.S3 对象的 Golang 单元测试

在 Go 泛型中,如何对联合约束中的类型使用通用方法?