在 Web 系统中,同一个客户端(无论是用户还是服务)在短时间内发出多个请求是很常见的。根据流量大小,这可能会导致服务器过载、处理缓慢,甚至对于无法应对这么多请求的系统,会引发系统故障。
速率限制 是一种技术,用来控制客户端在一段时间内向服务器发送请求数量。它像一个“守门人”,限制在一定时间内对API或服务的调用。
为什么使用限流器?
在应用程序中使用限流机制有以下几个好处和原因:
1 - 滥用防护:防止客户端进行过多或恶意的请求,比如DoS攻击,导致系统过载。
2 - 资源使用均衡:限制请求数量有助于确保服务器资源得到有效利用,同时避免影响其他用户。
3 - 提高性能:通过限制请求量,您可以确保应用程序在大量并发访问下依然流畅运行。
4 - 用户体验:通过限制过量的请求流量,防止用户被封锁或遇到应用运行缓慢。
5 - 安全 : 通过限制每秒请求数量来帮助阻止暴力破解尝试,防止暴力破解攻击。
一个使用 Go 和 Chi 的限流器实例
接下来,我们来看一个在使用chi包来管理路由的Go应用里实现限速器的例子。
我们将使用golang.org/x/time/rate这个包,该包提供了基于令牌桶算法的工具,用来创建、管理和维护速率限制器(限速器)。
以下代码展示了如何在使用 Go 的 http.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()
// 全局应用 RateLimiter,限制每秒5个请求,突发请求最多10个
r.Use(RateLimiter(rate.Limit(5), 10, 1*time.Second))
// 定义一个测试用的路由
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("请求已成功"))
})
http.ListenAndServe(":3000", r)
}
// RateLimiter 中间件用于限制客户端请求
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) {
// 捕获客户端 IP
ip := strings.Split(r.RemoteAddr, ":")[0]
// 检查该 IP 是否已配置了限速器
limiter, exists := limiterMap[ip]
if !exists {
limiter = rate.NewLimiter(limit, burst)
limiterMap[ip] = limiter
}
// 检查客户端是否在最近有过请求,并在必要时等待超时
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": "请求过多,请稍后再试"})
return
}
// 检查是否已达到限速
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": "请求过多,请稍后再试"})
}
// 如果一切正常,则继续处理
next.ServeHTTP(w, r)
})
}
}
全屏,退出全屏
这段代码是怎么工作的?
一 - IP 地址映射和限制 :代码使用了两个映射表:一个用于存储限流器(limiterMap
),另一个用于记录每个 IP 的最后一次请求(lastRequestMap
)。从 r.RemoteAddr
变量中获取客户端的 IP 地址。
2 - 请求控制:对于每个请求,检查该IP是否已经配置了速率限制器。如果没有,就创建一个新的速率限制器,并设置定义的限制和突发值。
3 - 检查:在允许请求通过前,代码会检查客户端是否试图在定义的时间间隔内发起新的请求。如果时间不够,它会返回 429 (请求过多) 错误。
4 - 请求限制规则:如果已经达到限制,rate.Limiter.Allow()
会阻止请求并返回 429 错误;否则,客户端可以发送请求。rate.Limiter.Allow()
是一个检查客户端是否可以发送请求,或者是否已经达到限制的方法。
- 突发请求:这是客户端在无需等待限额恢复的情况下可以立即发送的最大请求数,它定义了超出限流器设定的正常速率的“容差”突发流量。
当试图短时间内发出很多请求时,会收到这样的错误提示:
限速器可以使用什么?
速率限制是一种可以通过多种方式应用的技术,用于识别和限制客户端的使用量。在我们这里,我们通过客户端的IP地址来跟踪请求并施加限制措施。然而,根据应用程序的类型和具体使用场景,还有其他方法。这里有一些可能的方法:
1 - 按 IP 地址(如示例所示)
- 适用于限制来自特定IP地址的请求。它在公共API或由各种客户端访问的应用程序中非常实用。
- 优点:实现简单,可以有效防止同一IP地址的滥用。
- 局限性:在共享网络(如NAT)中效果不佳,因为在这些网络中,多个用户共享同一个IP地址。
2 - 通过令牌认证。
- 非常适合使用令牌(如 JWT、OAuth 等)进行用户认证的 API。在这种场景下,你可以根据客户端发送的令牌来追踪请求。
- 优势:更细致,即使在共享网络环境下也能区分用户。
- 限制:需要应用程序支持身份验证机制。
3 - 通过客户端ID:
- 在提供每个客户端访问密钥(如API密钥)的API中比较常见。
- 优点:在服务集成时,每个客户端都用一个唯一的密钥来标识时效果不错。
- 局限性:如果密钥被共享或暴露,则无法阻止被滥用。
4 - 用户每次会话:
- 在需要使用会话的应用程序中(cookies 或会话令牌),可以使用唯一的会话ID来限制请求。
- 好处在于,它关注的是应用程序中每个用户的单独体验。
- 缺点是需要管理和存储这些标识符。
5 - 按路径或端点 :
1 - 有助于限制对特定高负载端点(如搜索或上传)的调用次数。可以与其他策略(例如基于IP地址或令牌的策略)结合使用。
2 - 好处是:保护关键端点不受滥用影响。
4 - 缺点是:需要为每个路由进行细致配置。
还有其他控制方法。
在代码示例中,我们使用IP地址 (r.RemoteAddr
),因为在公共API场景或客户端识别不依赖令牌或会话的应用程序中,使用IP地址 (r.RemoteAddr
) 是一种简单且高效的做法。
最后的思考
实现速率限制器是确保应用程序的安全性和稳定性以及提供更均衡用户体验的有效方法。如前所示,我们展示了如何高效地集成它,以限制客户端请求。
如前所述,这是保护您的服务免受恶意使用和恶意攻击并确保流量分配合理的必要技术。
或:相关内容:
可以在这里看到我的博客上的这篇帖子https://wiliamvj.com/en/posts/rate-limiter/
共同学习,写下你的评论
评论加载中...
作者其他优质文章