Home > Article > Backend Development > Rate Limiting a Golang API using Redis
用更简单的话来说,速率限制,它是一种限制用户或客户端在给定时间范围内可以向 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 ( "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 }
Let's first directly jump to the rateLimiter() function and understand it. This function asks for an argument that is the IP address of the request which we can obtain via c.ClientIP() in the main function. And we return an error if there is limit is hit otherwise keep it nil. Most of the code is boilerplate code which we took from the official GitHub repo. The key functionality to look closer into here is the limiter.Allow() function. Addr: takes the URL path value for the Redis instance. I am using Docker to run it locally. You can use anything, just make sure you replace the URL accordingly.
res, err := limiter.Allow(ctx, clientIP, redis_rate.PerMinute(10))
It takes three arguments, the first is ctx, the second one is Key, Key (key for a value) for the Redis Database, and the third one is the the limit. So, the function stores the clientIP address as a key and the default limit as the value and reduces it when a request is made. The reason for this structure is that the Redis database needs unique identification and a unique key for storing key-value pairs kind of data, and every IP address is unique in its own way, this is why we are using IP addresses instead of usernames, etc. The 3rd argument redis_rate.PerMinute(10) can be modified as per our need, we can set limit PerSecond, PerHour, etc, and set the value inside parentheses for how many requests can be made per minute/second/hour. In our case, it's 10 per minute. Yes, it's that simple to set.
At last, we are checking if there is a remaining quota of not by res.Remaining. If it's zero we will return an error with the message otherwise we'll return nil. For eg, you can also do res.Limit.Rate to check the limit rate, etc. You can play around and dig deeper into that.
Now coming the main() function:
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") }
Everything is almost the same. In the /message route, now every time the route gets hit, we call the rateLimit() function and pass it a ClientIP address and store the return value (error) value in the err variable. If there is an error we will return a 429, that is, http.StatusTooManyRequests, and a message "message": "You have hit the limit". If the person has a remaining limit and the rateLimit() returns no error it will work normally, as it did earlier and serve the request.
That was all the explanation. Let's now test the working. Re-run the server by executing the same command. For the 1st time, we will see the same message we got earlier. Now refresh your browser 10 times (As we set a limit of 10 per minute), and you will see the error message in the browser.
We can also verify this by seeing the logs in the terminal. Gin offers great logging out of the box. After a minute it will restore our limit quota.
That's come to the end of this blog, I hope you enjoy reading as much as I enjoy writing this. I am glad you made it to the end—thank you so much for your support. I also talk regularly about Golang and other stuff like Open Source and Docker on X (Twitter). You can connect me over there.
The above is the detailed content of Rate Limiting a Golang API using Redis. For more information, please follow other related articles on the PHP Chinese website!