美文网首页go
【Go Web开发】API限流

【Go Web开发】API限流

作者: Go语言由浅入深 | 来源:发表于2022-02-28 21:25 被阅读0次

如果您正在构建公共使用的API,那么很可能需要实现某种形式的限流,以防止客户端过快地发出大量请求,从而给服务器带来很大的压力。

接下来的内容我们将创建中间件来实现这一点。

本质上,这个中间件检查在过去N秒钟内服务器接收到多少请求,如果请求太多就向客户端发送"429 Too Many Requests"响应。我们需要将这个中间件放在业务处理程序之前,对请求被处理之前进行拦截,避免对请求进行JSON序列化或数据库查询等操作。

你将学习到:

  • 关于令牌桶限流算法背后的原理,以及我们如何在API或web应用程序的上下文中应用它们。
  • 如何创建中间件来对API请求限速,首先通过创建单个全局限流器,然后进行扩展支持基于IP地址的客户端限流。
  • 如何让限流器的参数在运行时可配置,包括关闭限流器等控制。

全局限流

我们先为应用程序创建一个全局限流器,然后慢慢深入。全局限流将考虑API接收到的所有请求(而不是为每个客户端设置单独的限速)。

可以利用x/time/rate包来帮助我们,而不是从头开始编写限流逻辑,因其非常复杂和耗时。x/time/rate包提供了经过测试的令牌桶限流器。先下载依赖包:

$ go get golang.org/x/time/rate@latest
go: downloading golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba 
go: added golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba

在开始写代码之前,我们介绍下令牌桶限流是如何工作的。x/time/rate包的官方文档描述:

限流器能控制事件发生的频率。限流器实现了一个大小为b的“令牌桶”,最初桶装满token并以每秒r个令牌的速率重新填充。

将这些内容放在API上下文中可以理解为:

  • 开始有一个包含b个tokens的桶。
  • 每当我们接收到HTTP请求时,我们将从桶中删除一个令牌。
  • 每隔1/r秒,将一个令牌添加回桶中——最多可达b个令牌总数。
  • 如果我们收到一个HTTP请求,而桶是空的,那么我们应该返回一个429 Too Many Requests响应。

在实践中,这意味着我们的应用程序允许最大“突发”b个连续的HTTP请求,但随着时间的推移,它将允许平均每秒r个请求。

根据x/time/rate包,为了创建一个令牌桶限流器,我们需要使用NewLimiter()函数,其包含如下参数:

 // Limit类型是float64的别名
func NewLimiter(r Limit, b int) *Limiter

因此,如果我们要创建一个每秒允许2个请求,支持4个突发请求的限流器,我们可以使用如下代码实现:

//每秒允许2个请求,一次最多4个请求
limiter := rate.NewLimiter(2, 4)

执行全局限流

前面我们从宏观角度解释限流,下面让我们进入代码,看看它在实践中是如何工作的。

使用中间件模式的一个优点是,它可以包含“初始化”代码,当我们用中间件包装一些东西时,它只运行一次,就可以处理所有的请求。

func (app *application) exampleMiddleware(next http.Handler) http.Handler {
    //当使用中间件封装后,这里代码只运行一次
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.request) {
        //这里代码在每个请求到来都会执行
        next.ServerHTTP(w, r)
    })
}

在我们的例子中,将创建一个rateLimit()中间件方法,它将创建一个新的限流器作为“初始化”代码的一部分,然后对随后处理的每个请求使用这个限流器。打开cmd/api/middleware.go文件并创建以下中间件:

package main

...

func (app *application)rateLimit(next http.Handler) http.Handler {
    //初始化限流器
    limit := rate.NewLimiter(2, 4)
    //运行的是一个闭包
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        //调用limit.Allow()函数检查请求是否允许,不允许就返回429
        if !limit.Allow(){
            app.rateLimitExceededResponse(w, r)
            return
        }
        next.ServeHTTP(w, r)
    })
}

在这段代码中,每当调用限流器上的Allow()方法时,只会从桶中消耗一个令牌。如果桶中没有令牌,那么Allow()将返回false,以此触发向客户端返回429 Too Many Requests响应。

要注意的是Allow()方法实现使用了互斥锁保护,并发使用是安全的。

下面到cmd/api/errors.go文件创建rateLimitExeededRespose()帮助函数:

package main

...

func (app *application) rateLimitExceededResponse(w http.ResponseWriter, r *http.Request) {
    message := "rate limit exceeded"
    app.errorResponse(w, r, http.StatusTooManyRequests, message)
}

最后,在cmd/api/routes.go文件中需要将rateLimit()中间件添加到中间件服务链中。它应该在我们的panicRecovery中间件之前运行(以便恢复rateLimit()中有任何panic发生),但是应该尽早使用它,以防止服务器进行不必要的工作。

File: cmd/api/routes.go

package main

...

func (app *application) routes() http.Handler {
    router := httprouter.New()

    router.NotFound = http.HandlerFunc(app.notFoundResponse)
    router.MethodNotAllowed = http.HandlerFunc(app.methodNotAllowedResponse)

    router.HandlerFunc(http.MethodGet, "/v1/healthcheck", app.healthcheckHandler)

    router.HandlerFunc(http.MethodGet, "/v1/movies", app.listMoviesHandler)
    router.HandlerFunc(http.MethodPost, "/v1/movies", app.createMovieHandler)
    router.HandlerFunc(http.MethodGet, "/v1/movies/:id", app.showMovieHandler)
    router.HandlerFunc(http.MethodPatch, "/v1/movies/:id", app.updateMovieHandler)
    router.HandlerFunc(http.MethodDelete, "/v1/movies/:id", app.deleteMovieHandler)
        //用ratelimit()中间件处理router
    return app.recoverPanic(app.rateLimit(router))
}

现在我们应该准备好进行测试了!重启服务,然后在另一个终端窗口执行以下命令批量发送请求到GET /v1/healthcheck接口。你会看到如下响应:

$ for i in {1..6}; do curl http://localhost:4000/v1/healthcheck; done
{
        "status": "available",
        "system_info": {
                "environment": "development",
                "version": "1.0.0"
        }
}
{
        "status": "available",
        "system_info": {
                "environment": "development",
                "version": "1.0.0"
        }
}
{
        "status": "available",
        "system_info": {
                "environment": "development",
                "version": "1.0.0"
        }
}
{
        "status": "available",
        "system_info": {
                "environment": "development",
                "version": "1.0.0"
        }
}
{
        "error": "rate limit exceeded"
}
{
        "error": "rate limit exceeded"
}

从这里我们可以看到,前4个请求成功了,因为我们的限流器设置为允许接收“突发”4个请求。一旦这4个请求被用完,桶中的令牌已经用完,API将返回“rate limit exceeded”错误响应。

如果您等待一秒钟并重新运行此命令,您应该会发现第二批中的一些请求再次成功,这是因为令牌桶以每秒两个令牌的速度重新填充到桶中。

相关文章

  • 【Go Web开发】API限流

    如果您正在构建公共使用的API,那么很可能需要实现某种形式的限流,以防止客户端过快地发出大量请求,从而给服务器带来...

  • 【Go Web开发】基于IP限流

    上一篇文章[https://www.jianshu.com/p/f7a03b77253b]我们介绍了全局限流,当您...

  • 【语言学习】Go语言之API开发Gin框架

    1 gin框架介绍 gin框架是Go语言进行web开发(api开发,微服务开发)框架中,功能和Martini框架类...

  • 1.beego框架简介

    beego 简介 beego 是一个快速开发 Go 应用的 HTTP 框架,他可以用来快速开发 API、Web 及...

  • 初识beego

    beego简介 beego 是一个快速开发 Go 应用的 HTTP 框架,他可以用来快速开发 API、Web 及后...

  • 拆箱phper最适合入门的go框架beego

    beego beego 是一个快速开发 Go 应用的 HTTP 框架,他可以用来快速开发 API、Web 及后端服...

  • beego入门

    简介: beego 是一个快速开发 Go 应用的 HTTP 框架,他可以用来快速开发 API、Web 及后端服务等...

  • 【实践】手把手教你入门BEEGO框架

    1,摘要 beego 是一个快速开发 Go 应用的 HTTP 框架,他可以用来快速开发 API、Web 及后端服务...

  • beego框架学习(一)

    beego简介 Beego是一个快速开发Go应用的http框架,可用于快速开发Api、web及后端服务等各种应用,...

  • 借助URLOS快速安装beego web框架

    简介 beego 是一个快速开发 Go 应用的 HTTP 框架,他可以用来快速开发 API、Web 及后端服务等各...

网友评论

    本文标题:【Go Web开发】API限流

    本文链接:https://www.haomeiwen.com/subject/cbkrrrtx.html