Redis Rate Limiter 使用指南
简介
github.com/go-redis/redis_rate/v10
是一个基于 Redis 的分布式限流库,专为 Go 语言设计,可以轻松实现 API 限流、请求节流等功能。它基于令牌桶算法,支持多种限流策略,适用于分布式系统中的流量控制。
安装
go get github.com/go-redis/redis_rate/v10
基本用法
初始化限流器
import (
"context"
"github.com/redis/go-redis/v10"
"github.com/go-redis/redis_rate/v10"
)
// 创建 Redis 客户端
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
// 创建限流器
limiter := redis_rate.NewLimiter(rdb)
简单限流示例
// 限制每分钟最多允许 10 次请求
res, err := limiter.Allow(context.Background(), "user:123", redis_rate.PerMinute(10))
if err != nil {
// 处理错误
}
if res.Allowed > 0 {
// 允许请求
fmt.Println("请求被允许")
} else {
// 拒绝请求
fmt.Printf("请求被拒绝,请在 %v 后重试\n", res.RetryAfter)
}
限流策略
预定义的限流策略
// 每秒限制
redis_rate.PerSecond(10) // 每秒最多 10 个请求
// 每分钟限制
redis_rate.PerMinute(100) // 每分钟最多 100 个请求
// 每小时限制
redis_rate.PerHour(1000) // 每小时最多 1000 个请求
自定义限流策略
// 创建自定义限流策略
limit := redis_rate.Limit{
Rate: 20, // 每个时间单位允许的请求数
Burst: 5, // 突发请求数(令牌桶容量)
Period: time.Minute * 5, // 时间单位
}
res, err := limiter.Allow(ctx, "custom:limit", limit)
高级用法
批量限流检查
// 批量检查多个限流键
keys := []string{"user:1", "user:2", "user:3"}
limits := make([]redis_rate.Limit, len(keys))
for i := range limits {
limits[i] = redis_rate.PerMinute(10)
}
results, err := limiter.AllowN(ctx, keys, limits, 1)
if err != nil {
// 处理错误
}
for i, res := range results {
fmt.Printf("Key: %s, Allowed: %d, Remaining: %d\n",
keys[i], res.Allowed, res.Remaining)
}
消耗多个令牌
// 一次性消耗 5 个令牌
res, err := limiter.AllowN(ctx, "heavy:operation", redis_rate.PerMinute(20), 5)
在 Web 框架中使用
Gin 框架集成示例
func RateLimitMiddleware(limiter *redis_rate.Limiter) gin.HandlerFunc {
return func(c *gin.Context) {
key := "rate_limit:" + c.ClientIP()
res, err := limiter.Allow(c.Request.Context(), key, redis_rate.PerMinute(60))
if err != nil {
c.AbortWithStatusJSON(500, gin.H{"error": "限流服务异常"})
return
}
if res.Allowed == 0 {
c.AbortWithStatusJSON(429, gin.H{
"error": "请求过于频繁",
"retry_after": res.RetryAfter.Seconds(),
})
return
}
// 设置剩余请求数的响应头
c.Header("X-RateLimit-Remaining", fmt.Sprintf("%d", res.Remaining))
c.Header("X-RateLimit-Limit", "60")
c.Next()
}
}
返回值解析
Allow
方法返回的 redis_rate.Result
结构包含以下字段:
type Result struct {
// Allowed 表示允许的请求数量(0 或 1)
Allowed int
// Remaining 表示剩余的令牌数量
Remaining int
// RetryAfter 表示需要等待多久才能获取下一个令牌
RetryAfter time.Duration
// ResetAfter 表示多久后令牌桶会重置
ResetAfter time.Duration
}
分布式系统中的应用
多服务实例共享限流状态
// 所有服务实例使用相同的 Redis 服务器
// 确保限流状态在所有实例间共享
rdb := redis.NewClient(&redis.Options{
Addr: "redis-server:6379",
})
limiter := redis_rate.NewLimiter(rdb)
// 使用统一的键前缀
keyPrefix := "api:rate:"
key := keyPrefix + userID
res, err := limiter.Allow(ctx, key, redis_rate.PerMinute(100))
按不同维度限流
// 按用户 ID 限流
userKey := "user:" + userID
userRes, _ := limiter.Allow(ctx, userKey, redis_rate.PerMinute(60))
// 按 IP 限流
ipKey := "ip:" + clientIP
ipRes, _ := limiter.Allow(ctx, ipKey, redis_rate.PerMinute(100))
// 按 API 路径限流
pathKey := "path:" + requestPath
pathRes, _ := limiter.Allow(ctx, pathKey, redis_rate.PerSecond(10))
// 综合判断
if userRes.Allowed > 0 && ipRes.Allowed > 0 && pathRes.Allowed > 0 {
// 允许请求
}
最佳实践
- 合理设置限流参数:根据业务需求和系统承载能力设置合适的限流阈值
- 使用有意义的键名:为限流键使用有意义的前缀和命名规则
- 监控限流情况:记录被限流的请求,以便分析和调整限流策略
- 优雅处理限流:当请求被限流时,返回友好的错误信息和重试建议
- 设置合理的超时:为 Redis 操作设置合理的超时时间,避免因 Redis 故障影响整体服务
示例:完整的限流中间件
package middleware
import (
"context"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/redis/go-redis/v10"
"github.com/go-redis/redis_rate/v10"
)
// RateLimiterConfig 限流器配置
type RateLimiterConfig struct {
// 每分钟允许的请求数
RequestsPerMinute int
// 是否启用 IP 限流
EnableIPLimit bool
// 是否启用路径限流
EnablePathLimit bool
}
// NewRateLimiter 创建限流中间件
func NewRateLimiter(rdb *redis.Client, config RateLimiterConfig) gin.HandlerFunc {
limiter := redis_rate.NewLimiter(rdb)
return func(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), time.Second*3)
defer cancel()
// 默认限流键(用户标识或会话ID)
key := "rate:default:" + c.GetString("user_id")
if key == "rate:default:" {
key = "rate:session:" + c.GetString("session_id")
}
// IP 限流
if config.EnableIPLimit {
ipKey := "rate:ip:" + c.ClientIP()
ipRes, err := limiter.Allow(ctx, ipKey, redis_rate.PerMinute(config.RequestsPerMinute*2))
if err != nil || ipRes.Allowed == 0 {
c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
"error": "IP 请求频率超限",
"retry_after": ipRes.RetryAfter.Seconds(),
})
return
}
}
// 路径限流
if config.EnablePathLimit {
pathKey := "rate:path:" + c.FullPath()
pathRes, err := limiter.Allow(ctx, pathKey, redis_rate.PerMinute(config.RequestsPerMinute*5))
if err != nil || pathRes.Allowed == 0 {
c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
"error": "接口请求频率超限",
"retry_after": pathRes.RetryAfter.Seconds(),
})
return
}
}
// 用户/会话限流
res, err := limiter.Allow(ctx, key, redis_rate.PerMinute(config.RequestsPerMinute))
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"error": "限流服务异常",
})
return
}
if res.Allowed == 0 {
c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
"error": "请求过于频繁,请稍后再试",
"retry_after": res.RetryAfter.Seconds(),
})
return
}
// 设置响应头
c.Header("X-RateLimit-Limit", fmt.Sprintf("%d", config.RequestsPerMinute))
c.Header("X-RateLimit-Remaining", fmt.Sprintf("%d", res.Remaining))
c.Header("X-RateLimit-Reset", fmt.Sprintf("%.0f", res.ResetAfter.Seconds()))
c.Next()
}
}