手记

用Golang和Redis限制API请求频率

用更简单的话来说,速率限制 是一种技术,限制用户或客户端在一定时间内对API的请求数量。你可能以前访问过天气预报或笑话API时遇到过“速率限制已超出”的消息。关于为什么要对API进行速率限制,有很多理由,其中一些重要的是确保安全、防止资源超载、公平使用等。

在这篇博客中,我们将创建一个使用Golang和Gin框架的HTTP服务器,并使用Redis为某个端点添加请求速率限制的功能。我们还将记录某个IP地址在一段时间内对服务器的请求总次数,并在请求次数超过我们设定的限制时返回一个错误消息。

如果你还不了解Gin和Redis的话。Gin 是一个用Golang编写的web框架。它可以帮助你快速构建一个简单的服务器,无需写出大量代码。Redis 是一个内存中的键值数据存储系统,可以作为数据库或缓存层使用。

前提条件

  • 熟悉 Golang、Gin 和 Redis
  • 一个 Redis 实例(可以使用 Docker 或远程机器来运行)

开始入门:让我们开始吧

要初始化项目,运行 go mod init <GitHub 路径>,例如 go mod init github.com/Pradumnasaraf/go-redis

那么让我们来创建一个简单的HTTP服务器,用Gin框架,然后为其应用速率限制逻辑。下面的代码你可以复制。这非常基础。当请求/message端点时,服务器会回复消息。

复制下方代码后,,然后就可以执行 go mod tidy 命令(这个命令会整理并安装我们用到的那些包)。

    package main

    import (
        "github.com/gin-gonic/gin"
    )

    func main() {
        r := gin.Default()
        r.GET("/message", func(c *gin.Context) {
            c.JSON(200, gin.H{
                "message": "你可以继续发起更多请求",
            })
        })
        r.Run(":8081") // Run on localhost:8081
    } 

切换到全屏退出全屏

我们可以在终端中输入 go run main.go 并运行,然后在终端中看到消息。


点击这里查看图片!

要测试它,只需在浏览器中访问 localhost:8081/message,就能看到消息。

浏览器截图如下:

现在我们的服务器已经启动并运行了,让我们为 /message 路由设置一个限流功能。我们将使用 go-redis/redis_rate 包。感谢这个包的创建者,我们不需要自己动手写处理和检查限制的逻辑。它会为我们搞定这一切。

以下实现了限流功能后的完整代码。我们将逐部分理解代码。一开始就提供完整代码是为了避免混淆,并理解不同部分是如何协同作用的。

一旦你复制代码后,请运行 go mod tidy 来安装所有导入的包。现在让我们来理解代码,下面我们将一起看这段代码。

package main

import (
    "context"
    "errors"
    "net/http"

    "github.com/gin-gonic/gin"
    "github.com/go-redis/redis_rate/v10"
    "github.com/redis/go-redis/v9"
)

func main() {
    r := gin.Default()
    r.GET("/message", func(c *gin.Context) {
        err := rateLimiter(c.ClientIP())
        if err != nil {
            c.JSON(http.StatusTooManyRequests, gin.H{
                "message": "您已达速率限制",
            })
            return
        }
        c.JSON(http.StatusOK, gin.H{
            "message": "您可以继续发送更多请求",
        })
    })
    r.Run(":8081")
}

func rateLimiter(clientIP string) error {
    ctx := context.Background()
    rdb := redis.NewClient(&redis.Options{
        Addr: "localhost:6379",
    })

    limiter := redis_rate.NewLimiter(rdb)
    res, err := limiter.Allow(ctx, clientIP, redis_rate.PerMinute(10))
    if err != nil {
        panic(err)
    }
    if res.Remaining == 0 {
        return errors.New("您已达速率限制")
    }

    return nil
}

进入全屏,退出全屏

让我们直接跳到 rateLimiter() 函数并理解它。这个函数需要一个参数,即请求的 IP 地址,我们可以通过 c.ClientIP()main 函数中获取。如果达到限制,则返回一个错误,否则返回 nil。大部分代码是从官方 GitHub repo 中复制来的样板代码。这里的关键功能是 limiter.Allow() 函数。Addr: 使用 URL 路径值来指定 Redis 实例。我使用 Docker 在本地运行 Redis 实例。你可以使用任何工具运行 Redis 实例,只需确保替换相应的 URL 即可。

    res, err := limiter.Allow(ctx, clientIP, redis_rate.PerMinute(10))

全屏模式,然后退出全屏

它接受三个参数,第一个是 ctx,第二个是 Key,即 Redis 数据库中的键(用于一个值的键),第三个是限制。因此,该函数会将 clientIP 地址作为键,将默认限制作为值存储,并在每次请求时减少计数。这种结构的原因在于 Redis 数据库需要唯一的标识符和唯一的键来存储键值对形式的数据,而每个 IP 地址都是唯一的,这就是为什么我们使用 IP 地址而不是用户名等。可以修改第三个参数redis_rate.PerMinute(10),我们可以设置每秒、每分钟或每小时的请求限制,并在括号内指定允许的请求数量。在我们的情况下,它是每分钟10次。是的,设置起来就这么简单。

最后,我们检查是否有剩余配额,通过 res.Remaining。如果为零,我们将返回一个带有消息的错误信息,否则返回 nil(空)。你可以通过 res.Limit.Rate 查询限制速率,等等。你可以自由探索,进一步挖掘更多。需要注意的是,这只是如何结合这两部分的一个例子。由于只有一个路由,我们没有使用任何中间件。如果有数十或数百个路由会怎样呢?

我们现在开始看 main() 函数。

    func main() {
        r := gin.Default()
        r.GET("/message", func(c *gin.Context) {
            err := rateLimiter(c.ClientIP())
            if err != nil {
                c.JSON(http.StatusTooManyRequests, gin.H{
                    "message": "您已达上限",
                })
                return
            }
            c.JSON(http.StatusOK, gin.H{
                "message": "您可以继续请求",
            })
        })
        r.Run(":8081")
    }

全屏模式,退出全屏

基本上一切都一样。每次有人访问这个路由时,我们都会调用 rateLimit() 函数,并传入 ClientIP 地址,将返回的错误存储在 err 变量中。如果有错误,我们将返回错误代码 429,即 http.StatusTooManyRequests,并附带消息 "message": "您已达到限制"。如果用户还有剩余的请求次数,并且 rateLimit() 无错返回,则一切正常,会处理请求。

解释到此为止。我们现在来测试一下工作。通过重新运行服务器并执行相同的命令。第一次刷新时,你会看到之前得到的相同消息。现在刷新你的浏览器10次(因为我们设定每分钟最多刷新10次),你会在浏览器中看到错误信息。

我们也可以通过查看终端的日志来验证。Gin 自带很好的日志记录功能。大约一分钟之后,它会自动恢复我们的配额。

这篇文章到这里就结束了,希望你喜欢这篇文章和我写它一样多。很高兴你能读到这儿——非常感谢你的支持,真的。我经常在X (推特)上聊Golang和其他话题,如开源项目和Docker。你也可以在那儿联系我。

0人推荐
随时随地看视频
慕课网APP