用更简单的话来说,速率限制,它是一种限制用户或客户端在给定时间范围内可以向 API 发出请求数量的技术。过去,当您尝试访问天气或笑话 API 时,您可能遇到过收到“超出速率限制”消息的情况。关于为什么要限制 API 的速率有很多争论,但一些重要的争论是合理使用它、确保其安全、保护资源免于过载等。
在本博客中,我们将使用 Gin 框架使用 Golang 创建一个 HTTP 服务器,使用 Redis 对端点应用速率限制功能,并存储某个时间范围内 IP 向服务器发出的请求总数。如果超过我们设置的限制,我们会给出错误消息。
如果你不知道 Gin 和 Redis 是什么。 Gin 是一个用 Golang 编写的 Web 框架。它有助于创建一个简单而快速的服务器,而无需编写大量代码。 Redis 它是一个内存中的键值数据存储,可以用作数据库或缓存功能。
现在,让我们开始吧。
要初始化项目,请运行 go mod init
然后让我们使用 Gin 框架创建一个简单的 HTTP 服务器,然后应用对其进行速率限制的逻辑。您可以复制下面的代码。这是非常基本的。当我们点击 /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": "You can make more requests", }) }) r.Run(":8081") //listen and serve on localhost:8081 }
我们可以通过在终端中执行 go run main.go 来运行服务器,并在终端中看到此消息。
要测试它,我们可以访问 localhost:8081/message 我们将在浏览器中看到此消息。
现在我们的服务器正在运行,让我们为 /message 路由设置速率限制功能。我们将使用 go-redis/redis_rate 包。感谢这个包的创建者,我们不需要从头开始编写处理和检查限制的逻辑。它将为我们完成所有繁重的工作。
下面是实现限速功能后的完整代码。我们会理解其中的每一点。只是尽早给出完整的代码,以避免任何混淆并了解不同部分如何协同工作。
复制代码后,运行 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": "You can make more requests", }) }) r.Run(":8081") //listen and serve on localhost:8081 }
我们先直接跳到rateLimiter()函数来理解一下。该函数需要一个参数,即请求的 IP 地址,我们可以通过主函数中的 c.ClientIP() 获取该地址。如果达到限制,我们将返回一个错误,否则将其保持为零。大部分代码是我们从官方 GitHub 存储库中获取的样板代码。这里需要仔细研究的关键功能是 limiter.Allow() 函数。 Addr:获取 Redis 实例的 URL 路径值。我正在使用 Docker 在本地运行它。您可以使用任何内容,只需确保相应地替换 URL 即可。
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": "you have hit the limit", }) return } c.JSON(http.StatusOK, gin.H{ "message": "You can make more requests", }) }) 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("Rate") } return nil }
它有三个参数,第一个是 ctx,第二个是 Key,Redis 数据库的 Key(值的键),第三个是限制。因此,该函数将 clientIP 地址存储为键,将默认限制存储为值,并在发出请求时减少它。这种结构的原因是Redis数据库需要唯一标识和唯一键来存储键值对类型的数据,并且每个IP地址都以自己的方式唯一,这就是为什么我们使用IP地址而不是用户名,第三个参数 redis_rate.PerMinute(10) 可以根据我们的需要进行修改,我们可以设置限制 PerSecond, PerHour,等等,并设置括号内的值,表示每分钟/秒/小时可以发出多少个请求。在我们的例子中,它是每分钟 10 个。是的,设置就这么简单。
最后,我们通过 res.Remaining 检查是否有剩余配额。如果它为零,我们将返回一个错误消息,否则我们将返回 nil。例如,您还可以执行 res.Limit.Rate 来检查限制率等。您可以尝试并深入研究它。
现在是 main() 函数:
res, err := limiter.Allow(ctx, clientIP, redis_rate.PerMinute(10))
一切都几乎一样。在 /message 路由中,现在每次路由命中时,我们都会调用rateLimit()函数并向其传递一个 ClientIP 地址,并将返回值(错误)值存储在 err 变量中。如果出现错误,我们将返回 429,即 http.StatusTooManyRequests,以及一条消息“message”:“您已达到限制”。如果该人有剩余限制并且rateLimit()没有返回错误,它将正常工作,就像之前所做的那样并服务于请求。
这就是所有的解释。现在让我们测试一下工作情况。通过执行相同的命令重新运行服务器。我们将第一次看到之前收到的相同消息。现在刷新您的浏览器 10 次(因为我们设置了每分钟 10 次的限制),您将在浏览器中看到错误消息。
我们还可以通过查看终端中的日志来验证这一点。 Gin 提供了很好的开箱即用的登录功能。一分钟后它将恢复我们的限制配额。
本博客到此结束,我希望您喜欢阅读,就像我喜欢写这篇文章一样。我很高兴您能坚持到最后,非常感谢您的支持。我还经常在 X (Twitter) 上谈论 Golang 以及其他内容,例如开源和 Docker。你可以在那里联系我。
以上是使用 Redis 对 Golang API 进行速率限制的详细内容。更多信息请关注PHP中文网其他相关文章!