字节笔记本

2026年2月22日

gin-redis-ip-limiter:基于 Redis 的 Gin 框架 IP 限流中间件

本文介绍 gin-redis-ip-limiter,一个基于 Redis 的 Gin 框架 IP 限流中间件。该项目使用滑动窗口算法实现高效的请求限流,帮助开发者保护 API 接口免受恶意请求和流量冲击。

项目简介

gin-redis-ip-limiter 是一个开源的 Go 语言中间件项目,由 Salvatore Giordano 开发维护。该项目基于 Gin Web 框架,利用 Redis 的有序集合(Sorted Set)数据结构实现滑动窗口限流算法。截至目前,该项目在 GitHub 上已获得 24 stars,12 个 fork,主要使用 Go 语言编写。

该项目的核心设计灵感来自 Engagor 博客的滑动窗口限流文章,使用 go-redis 作为 Redis 客户端库。

核心特性

  • 滑动窗口限流算法:基于 Redis 有序集合实现精确的滑动窗口计数,避免固定窗口的临界突刺问题
  • IP 级别的限流控制:根据客户端 IP 地址进行请求限制,有效防止单 IP 的恶意请求
  • 可插拔设计:只需传入 Redis 客户端即可使用,支持自定义限流键、请求上限和时间窗口
  • Gin 框架原生支持:作为 Gin 中间件使用,集成简单,符合 Gin 的 HandlerFunc 规范
  • 轻量级实现:核心代码仅约 40 行,无冗余依赖,性能高效

技术栈

  • Go - 主要编程语言
  • Gin - Web 框架
  • Redis - 分布式缓存和限流数据存储
  • go-redis - Redis 客户端库

安装指南

前置要求

  • Go >= 1.11(支持 Go Modules)
  • Redis 服务器

安装步骤

bash
go get -u github.com/Salvatore-Giordano/gin-redis-ip-limiter/

快速开始

go
package main

import (
    "time"

    "github.com/Salvatore-Giordano/gin-redis-ip-limiter"
    "github.com/gin-gonic/gin"
    "github.com/go-redis/redis"
)

func main() {
    r := gin.Default()

    // 配置限流中间件
    r.Use(iplimiter.NewRateLimiterMiddleware(
        redis.NewClient(&redis.Options{
            Addr:     "localhost:6379",
            Password: "",
            DB:       1,
        }),
        "general",      // Redis 键前缀
        200,            // 请求上限
        60*time.Second, // 滑动窗口时长
    ))

    // ... 你的路由定义

    r.Run(":8080")
}

使用示例

场景 1:API 接口限流

go
package main

import (
    "net/http"
    "time"

    "github.com/Salvatore-Giordano/gin-redis-ip-limiter"
    "github.com/gin-gonic/gin"
    "github.com/go-redis/redis"
)

func main() {
    r := gin.Default()

    redisClient := redis.NewClient(&redis.Options{
        Addr: "localhost:6379",
    })

    // 为 API 路由组添加限流
    api := r.Group("/api")
    api.Use(iplimiter.NewRateLimiterMiddleware(
        redisClient,
        "api",
        100,            // 每分钟最多 100 次请求
        60*time.Second,
    ))
    {
        api.GET("/users", getUsers)
        api.POST("/orders", createOrder)
    }

    r.Run(":8080")
}

func getUsers(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{"users": []string{"user1", "user2"}})
}

func createOrder(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{"order_id": "12345"})
}

场景 2:不同接口差异化限流

go
func main() {
    r := gin.Default()

    redisClient := redis.NewClient(&redis.Options{
        Addr: "localhost:6379",
    })

    // 登录接口:每分钟 5 次(防止暴力破解)
    loginLimiter := iplimiter.NewRateLimiterMiddleware(
        redisClient,
        "login",
        5,
        60*time.Second,
    )

    // 普通接口:每分钟 200 次
    generalLimiter := iplimiter.NewRateLimiterMiddleware(
        redisClient,
        "general",
        200,
        60*time.Second,
    )

    r.POST("/login", loginLimiter, handleLogin)
    r.GET("/data", generalLimiter, getData)

    r.Run(":8080")
}

API 参考

NewRateLimiterMiddleware

创建一个新的限流中间件。

go
func NewRateLimiterMiddleware(
    redisClient *redis.Client,
    key string,
    limit int,
    slidingWindow time.Duration,
) gin.HandlerFunc
参数类型说明
redisClient*redis.ClientRedis 客户端实例
keystringRedis 键前缀,数据存储格式为 ip:key
limitint在滑动窗口内允许的最大请求数
slidingWindowtime.Duration滑动窗口的时间长度

限流响应

当请求超过限制时,中间件会返回 HTTP 429 (Too Many Requests) 状态码:

json
{
    "status": 429,
    "message": "too many request"
}

实现原理

该中间件使用 Redis 有序集合(Sorted Set)实现滑动窗口限流:

  1. 清理过期数据:每次请求时,删除窗口时间外的旧记录
  2. 统计当前请求数:查询当前 IP 在窗口期内的请求数量
  3. 判断是否限流:如果请求数达到上限,返回 429 状态码
  4. 记录新请求:将当前请求时间戳添加到有序集合
  5. 设置过期时间:为 Redis 键设置与窗口时长相同的过期时间

这种实现方式相比固定窗口更加平滑,能够有效处理临界时刻的请求突刺问题。

注意事项

  • Redis 连接:确保 Redis 服务器可用,中间件初始化时会检查连接
  • 时间同步:分布式部署时,确保各服务器时间同步,避免限流不准确
  • 内存占用:Redis 会为每个 IP 存储时间戳数据,注意监控内存使用
  • 错误处理:Redis 连接失败会导致中间件 panic,建议在初始化时做好检查

项目链接

分享: