什么是速率限制器以及为什么要使用它?
在 Web 系统中,同一个客户端(无论是用户还是服务)在短时间内向服务器发出多个请求是很常见的。根据流量大小,这可能会导致服务器过载、处理速度缓慢,甚至系统无法处理如此大量的请求而导致故障。
速率限制器是一种用于控制客户端在给定时间段内向服务器发出的请求数量的技术。它充当“守门人”,限制在一定时间窗口内对 API 或服务的调用次数。
为什么要使用速率限制器?
在应用程序中使用速率限制器是一种很好的做法,原因如下:
1 - 滥用保护:防止客户端发出过多或恶意的请求,例如可能导致系统过载的拒绝服务(DoS)攻击。
2 - 资源使用平衡:限制请求数量有助于确保有效利用服务器资源并且不会损害其他用户。
3 - 提高性能:通过限制请求,您可以确保应用程序即使在大量同时访问的情况下也能继续顺利运行。
4 - 用户体验:通过限制过多的请求流量,可以防止用户被阻塞或遇到应用程序运行缓慢的情况。
5 - 安全性:通过限制每秒的请求数,帮助防止暴力攻击或试图利用漏洞。
在 Go 中使用速率限制器的实际示例
现在,让我们分析一个使用chi包在 Go 应用程序中实现速率限制器的示例,该包广泛用于管理路由。
我们将使用golang.org/x/time/rate包,它提供了基于令牌桶算法创建和管理速率限制器的工具。
下面的代码展示了如何在使用 Gohttp.Handler
控制客户端发出的请求数量的应用程序中实现此功能。
package main
import (
"encoding/json"
"net/http"
"strings"
"time"
"github.com/go-chi/chi"
"golang.org/x/time/rate"
)
func main() {
r := chi.NewRouter()
// Applies the RateLimiter globally, limiting 5 requests per second and a burst of 10 requests
r.Use(RateLimiter(rate.Limit(5), 10, 1*time.Second))
// Define a route for testing
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Requisição bem-sucedida"))
})
http.ListenAndServe(":3000", r)
}
// RateLimiter middleware to limit a client's requests
func RateLimiter(limit rate.Limit, burst int, waitTime time.Duration) func(next http.Handler) http.Handler {
limiterMap := make(map[string]*rate.Limiter)
lastRequestMap := make(map[string]time.Time)
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Capture the client IP
ip := strings.Split(r.RemoteAddr, ":")[0]
// Checks if the IP already has a Limiter configured
limiter, exists := limiterMap[ip]
if !exists {
limiter = rate.NewLimiter(limit, burst)
limiterMap[ip] = limiter
}
// Check if the client made a recent request and wait for the timeout if necessary
lastRequestTime, lastRequestExists := lastRequestMap[ip]
if lastRequestExists && time.Since(lastRequestTime) < waitTime {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusTooManyRequests)
json.NewEncoder(w).Encode(map[string]string{"error": "Too many requests"})
return
}
// Check if the limit has been reached
if !limiter.Allow() {
lastRequestMap[ip] = time.Now()
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusTooManyRequests)
json.NewEncoder(w).Encode(map[string]string{"error": "Too many requests"})
}
// If everything is ok, move on to the next handler
next.ServeHTTP(w, r)
})
}
}
代码如何工作?
1 - IP 映射和限制:代码使用两个映射:一个用于存储限制器(limiterMap
),另一个用于记录 IP 发出的最后一个请求(lastRequestMap
)。客户端 IP 是从r.RemoteAddr
变量中获取的。
2 - 请求控制:对于每个请求,系统都会检查 IP 是否已配置限制器。如果没有,则创建一个新的限制器,并设置相应的限制和突发流量。
3 - 检查:在允许请求通过之前,代码会检查客户端是否在定义的时间间隔内尝试发出新请求。如果时间不够,则返回429(请求过多)错误。
4 - 请求限制:该rate.Limiter.Allow()
方法用于检查客户端是否可以发出请求,或者是否已达到限制。如果达到限制,则阻止请求并返回 429 错误。
- 突发:这是客户端可以立即发出的最大请求数,无需等待限制补充。它定义了对超过速率限制器中配置的正常速率的流量峰值的“容忍度”。
当我们尝试在短时间内发出许多请求时,我们收到错误:
速率限制器有什么用途?
速率限制是一种可以通过多种方式识别和限制客户端使用情况的技术。在我们的示例中,我们使用客户端的 IP 地址作为键来跟踪请求并应用限制。但是,根据应用程序类型和使用场景,还有其他方法。以下是一些可能的方法:
1 – 通过 IP(如我们的示例):
- 非常适合限制来自特定地址的请求。它适用于公共 API 或各种客户端访问的应用程序。
- 优点:实现简单,有效防止同一地址的滥用。
- 限制:在多个用户共享同一 IP 的共享网络(如 NAT)中无法正常工作。
2 - 通过身份验证令牌:
- 适用于使用令牌(JWT、OAuth 等)进行用户身份验证的 API。在这种情况下,您可以根据客户端发送的令牌来跟踪请求。
- 优点:更细粒度,即使在共享网络上也可以区分用户。
- 限制:需要应用程序支持身份验证。
3 - 按客户端ID:
- 在为每个客户端提供访问密钥(例如 API 密钥)的 API 中很常见。
- 优点:适用于服务之间存在集成且每个客户端都由唯一键标识的场景。
- 限制:如果密钥被共享或暴露,则无法防止滥用。
4 – 每个用户会话:
- 在具有会话(cookie 或会话令牌)的应用程序中,可以使用唯一会话标识符来限制请求。
- 优点:专注于应用程序内的个人体验。
- 限制:需要管理会话和存储标识符。
5 - 按路线或终点:
1 - 适用于限制对特定高负载端点(例如搜索或上传)的调用。可与其他策略(例如 IP 或令牌)结合使用。2
- 优势:保护关键端点免遭滥用。4
- 局限性:需要对每条路由进行精细配置。
还有其他方法可以限制。
在代码示例中,我们使用 IP 地址(r.RemoteAddr
),因为对于公共 API 场景或不使用令牌或会话进行客户端识别的应用程序来说,这是一种简单有效的方法。
最后的考虑
实施速率限制器是确保应用程序安全性和稳定性,并提供更均衡的用户体验的有效方法。在上面的示例中,我们展示了如何集成它来有效地限制客户端请求。
这是一项至关重要的技术,可以保护您的服务免受滥用和攻击,并确保流量得到适当的分配。
链接
请参阅我的博客上的帖子
示例的存储库
鏂囩珷鏉ユ簮锛�https://dev.to/wiliamvj/what-is-rate-limiter-and-why-use-it-2c6