利用中间件限流

简介: 利用中间件限流

限流(Rate Limiting)是一种控制系统中请求速率的技术,用于防止过多的请求涌入系统,从而保护服务器资源,防止滥用或恶意攻击。实现限流中间件可以确保你的应用在高并发情况下依然稳定运行。

在 Go 语言中,结合 Gin 框架实现限流中间件是一种常见的做法。下面,我将详细介绍如何在 Gin 框架中手动实现限流中间件,采用令牌桶(Token Bucket)算法作为限流策略。

  1. 令牌桶算法概述
    令牌桶算法是一种常用的限流算法,其基本原理如下:

令牌桶:桶中有一定数量的令牌,代表可以处理的请求数。
生成令牌:按照固定的速率向桶中添加令牌,直到达到桶的容量。
处理请求:每当一个请求到达时,从桶中取出一个令牌,如果成功,允许请求继续处理;否则,拒绝请求。
这种算法允许突发请求,但在长时间内保持请求速率的稳定。

  1. 实现步骤
    2.1. 定义限流中间件
    我们将创建一个 Gin 中间件,该中间件使用令牌桶算法来限制每个客户端(基于 IP 地址)的请求速率。

2.2. 实现令牌桶
我们需要一个结构来表示每个客户端的令牌桶,包括:

速率:每秒生成的令牌数。
容量:令牌桶的最大容量。
剩余令牌:当前桶中剩余的令牌数。
最后更新时间:上一次令牌生成的时间。
2.3. 实现限流逻辑
在每个请求到达时:

根据客户端的标识(如 IP 地址)获取对应的令牌桶。
根据时间间隔生成新的令牌,确保令牌数量不超过桶的容量。
尝试获取一个令牌:
成功:允许请求继续处理。
失败:返回 429 状态码(Too Many Requests)。

  1. 代码实现
    下面是一个完整的示例代码,展示如何在 Gin 框架中实现基于 IP 地址的限流中间件。

package main

import (
"net/http"
"sync"
"time"

"github.com/gin-gonic/gin"

)

// TokenBucket 定义令牌桶结构
type TokenBucket struct {
rate float64 // 令牌生成速率(每秒生成的令牌数)
capacity float64 // 令牌桶容量
tokens float64 // 当前令牌数
lastRefill time.Time // 上次填充令牌的时间
mutex sync.Mutex // 保护令牌桶的互斥锁
}

// NewTokenBucket 创建一个新的令牌桶
func NewTokenBucket(rate float64, capacity float64) *TokenBucket {
return &TokenBucket{
rate: rate,
capacity: capacity,
tokens: capacity,
lastRefill: time.Now(),
}
}

// Allow 尝试获取一个令牌,返回是否允许
func (tb *TokenBucket) Allow() bool {
tb.mutex.Lock()
defer tb.mutex.Unlock()

now := time.Now()
elapsed := now.Sub(tb.lastRefill).Seconds()
tb.lastRefill = now

// 计算新增的令牌数
tb.tokens += elapsed * tb.rate
if tb.tokens > tb.capacity {
    tb.tokens = tb.capacity
}

if tb.tokens >= 1 {
    tb.tokens -= 1
    return true
}

return false

}

// RateLimiter 定义限流器结构
type RateLimiter struct {
clients map[string]*TokenBucket
mutex sync.Mutex
rate float64
capacity float64
}

// NewRateLimiter 创建一个新的限流器
func NewRateLimiter(rate float64, capacity float64) RateLimiter {
return &RateLimiter{
clients: make(map[string]
TokenBucket),
rate: rate,
capacity: capacity,
}
}

// GetTokenBucket 获取或创建客户端的令牌桶
func (rl RateLimiter) GetTokenBucket(clientID string) TokenBucket {
rl.mutex.Lock()
defer rl.mutex.Unlock()

tb, exists := rl.clients[clientID]
if !exists {
    tb = NewTokenBucket(rl.rate, rl.capacity)
    rl.clients[clientID] = tb
}

return tb

}

// Cleanup 定期清理不活跃的客户端(可选)
func (rl *RateLimiter) Cleanup(timeout time.Duration) {
for {
time.Sleep(timeout)
rl.mutex.Lock()
for clientID, tb := range rl.clients {
tb.mutex.Lock()
if time.Since(tb.lastRefill) > timeout {
delete(rl.clients, clientID)
}
tb.mutex.Unlock()
}
rl.mutex.Unlock()
}
}

// RateLimitMiddleware 返回一个 Gin 中间件,用于限流
func RateLimitMiddleware(rl RateLimiter) gin.HandlerFunc {
return func(c
gin.Context) {
clientIP := c.ClientIP()
tb := rl.GetTokenBucket(clientIP)

    if tb.Allow() {
        c.Next()
    } else {
        c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
            "error": "Too Many Requests",
        })
        return
    }
}

}

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

// 创建限流器,例:每秒5个请求,令牌桶容量为10
rateLimiter := NewRateLimiter(5, 10)

// 可选:启动清理协程,清理1分钟内不活跃的客户端
go rateLimiter.Cleanup(1 * time.Minute)

// 应用限流中间件
router.Use(RateLimitMiddleware(rateLimiter))

// 定义路由
router.GET("/", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "message": "Hello, World!",
    })
})

router.GET("/user/:name", func(c *gin.Context) {
    name := c.Param("name")
    c.JSON(http.StatusOK, gin.H{
        "message": "Hello, " + name + "!",
    })
})

// 启动服务器
router.Run(":8080")

}

  1. 代码详解
    4.1. 令牌桶结构
    type TokenBucket struct {
    rate float64
    capacity float64
    tokens float64
    lastRefill time.Time
    mutex sync.Mutex
    }
    rate:令牌生成速率,例如每秒生成5个令牌。
    capacity:令牌桶的最大容量,防止令牌数过多。
    tokens:当前令牌数。
    lastRefill:上次令牌生成的时间,用于计算新增的令牌数。
    mutex:互斥锁,确保并发安全。
    4.2. 令牌桶方法
    func (tb *TokenBucket) Allow() bool {
    tb.mutex.Lock()
    defer tb.mutex.Unlock()

    now := time.Now()
    elapsed := now.Sub(tb.lastRefill).Seconds()
    tb.lastRefill = now

    // 计算新增的令牌数
    tb.tokens += elapsed * tb.rate
    if tb.tokens > tb.capacity {

     tb.tokens = tb.capacity
    

    }

    if tb.tokens >= 1 {

     tb.tokens -= 1
     return true
    

    }

    return false
    }
    Allow():尝试获取一个令牌。
    计算自上次填充以来经过的时间,生成相应数量的令牌。
    如果令牌数超过容量,则设为容量。
    如果有足够的令牌,扣除一个并允许请求。
    否则,拒绝请求。
    4.3. 限流器结构
    type RateLimiter struct {
    clients map[string]TokenBucket
    mutex sync.Mutex
    rate float64
    capacity float64
    }
    clients:存储每个客户端(如 IP 地址)的令牌桶。
    mutex:互斥锁,确保并发安全。
    rate 和 capacity:全局的令牌生成速率和容量。
    4.4. 限流器方法
    func (rl
    RateLimiter) GetTokenBucket(clientID string) *TokenBucket {
    rl.mutex.Lock()
    defer rl.mutex.Unlock()

    tb, exists := rl.clients[clientID]
    if !exists {

     tb = NewTokenBucket(rl.rate, rl.capacity)
     rl.clients[clientID] = tb
    

    }

    return tb
    }
    GetTokenBucket():根据客户端标识(如 IP 地址)获取或创建令牌桶。
    func (rl *RateLimiter) Cleanup(timeout time.Duration) {
    for {

     time.Sleep(timeout)
     rl.mutex.Lock()
     for clientID, tb := range rl.clients {
         tb.mutex.Lock()
         if time.Since(tb.lastRefill) > timeout {
             delete(rl.clients, clientID)
         }
         tb.mutex.Unlock()
     }
     rl.mutex.Unlock()
    

    }
    }
    Cleanup():定期清理不活跃的客户端,防止内存泄漏。这个函数在一个独立的 goroutine 中运行。
    4.5. 限流中间件
    func RateLimitMiddleware(rl RateLimiter) gin.HandlerFunc {
    return func(c
    gin.Context) {

     clientIP := c.ClientIP()
     tb := rl.GetTokenBucket(clientIP)
    
     if tb.Allow() {
         c.Next()
     } else {
         c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
             "error": "Too Many Requests",
         })
         return
     }
    

    }
    }
    RateLimitMiddleware:Gin 中间件,获取客户端 IP,检查是否允许请求,允许则继续处理,不允许则返回 429 状态码。
    4.6. 主函数
    func main() {
    router := gin.Default()

    // 创建限流器,例:每秒5个请求,令牌桶容量为10
    rateLimiter := NewRateLimiter(5, 10)

    // 可选:启动清理协程,清理1分钟内不活跃的客户端
    go rateLimiter.Cleanup(1 * time.Minute)

    // 应用限流中间件
    router.Use(RateLimitMiddleware(rateLimiter))

    // 定义路由
    router.GET("/", func(c *gin.Context) {

     c.JSON(http.StatusOK, gin.H{
         "message": "Hello, World!",
     })
    

    })

    router.GET("/user/:name", func(c *gin.Context) {

     name := c.Param("name")
     c.JSON(http.StatusOK, gin.H{
         "message": "Hello, " + name + "!",
     })
    

    })

    // 启动服务器
    router.Run(":8080")
    }
    创建限流器:设置令牌生成速率为每秒5个,令牌桶容量为10个。
    启动清理协程:定期清理不活跃的客户端,防止内存泄漏。
    应用中间件:将限流中间件应用到 Gin 路由器上。
    定义路由:定义几个简单的路由进行测试。
    启动服务器:在端口8080上启动 Gin 服务器。

  2. 测试限流效果
    启动服务器后,可以使用 curl 或者其他 HTTP 客户端工具进行测试。

5.1. 正常请求
curl http://localhost:8080/
响应:

{
"message": "Hello, World!"
}
5.2. 超过限流
快速连续发送多个请求,超过令牌桶容量后,后续请求将被拒绝。

for i in {1..15}; do curl -i http://localhost:8080/; done
部分响应:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
...

HTTP/1.1 429 Too Many Requests
Content-Type: application/json; charset=utf-8
...

{
"error": "Too Many Requests"
}

  1. 进一步优化
    6.1. 限制不同的客户端
    当前实现基于 IP 地址进行限流。如果需要更精细的控制,可以基于用户身份、API 密钥等进行限流。

6.2. 分布式限流
上面的实现适用于单实例应用。如果你的应用部署在多个实例上,可以考虑使用分布式存储(如 Redis)来同步令牌桶状态,实现全局限流。

6.3. 使用第三方库
虽然手动实现限流中间件有助于理解其原理,但在生产环境中,使用成熟的第三方库更为可靠和高效。例如:

golang.org/x/time/rate:Go 官方提供的限流包,基于令牌桶算法。
uber-go/ratelimit:Uber 提供的高性能限流库。
示例:使用 golang.org/x/time/rate 实现限流中间件
package main

import (
"net/http"
"sync"
"time"

"github.com/gin-gonic/gin"
"golang.org/x/time/rate"

)

// Client 定义每个客户端的限流器
type Client struct {
limiter *rate.Limiter
lastSeen time.Time
}

// RateLimiter 使用 golang.org/x/time/rate 实现限流器
type RateLimiter struct {
clients map[string]*Client
mutex sync.Mutex
r rate.Limit
b int
}

// NewRateLimiter 创建一个新的限流器
func NewRateLimiter(r rate.Limit, b int) RateLimiter {
rl := &RateLimiter{
clients: make(map[string]
Client),
r: r,
b: b,
}

// 启动清理协程
go rl.cleanupClients()

return rl

}

// GetLimiter 获取或创建客户端的限流器
func (rl RateLimiter) GetLimiter(clientID string) rate.Limiter {
rl.mutex.Lock()
defer rl.mutex.Unlock()

client, exists := rl.clients[clientID]
if !exists {
    limiter := rate.NewLimiter(rl.r, rl.b)
    rl.clients[clientID] = &Client{
        limiter:  limiter,
        lastSeen: time.Now(),
    }
    return limiter
}

client.lastSeen = time.Now()
return client.limiter

}

// cleanupClients 定期清理不活跃的客户端
func (rl RateLimiter) cleanupClients() {
for {
time.Sleep(time.Minute)
rl.mutex.Lock()
for clientID, client := range rl.clients {
if time.Since(client.lastSeen) > 3
time.Minute {
delete(rl.clients, clientID)
}
}
rl.mutex.Unlock()
}
}

// RateLimitMiddleware 返回一个 Gin 中间件,使用 golang.org/x/time/rate 进行限流
func RateLimitMiddleware(rl RateLimiter) gin.HandlerFunc {
return func(c
gin.Context) {
clientIP := c.ClientIP()
limiter := rl.GetLimiter(clientIP)

    if limiter.Allow() {
        c.Next()
    } else {
        c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
            "error": "Too Many Requests",
        })
        return
    }
}

}

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

// 使用 golang.org/x/time/rate,每秒5个请求,桶容量10
rl := NewRateLimiter(5, 10)

// 应用限流中间件
router.Use(RateLimitMiddleware(rl))

// 定义路由
router.GET("/", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "message": "Hello, World!",
    })
})

[kod.w3c4.net)
[kod.wgooo.net)
[kod.willawen.com)
[kod.xyzytv.com)
[kod.wqcycpms.com)
[kod.wnear.com)
[kod.xinceshi.net)
router.GET("/user/:name", func(c *gin.Context) {
name := c.Param("name")
c.JSON(http.StatusOK, gin.H{
"message": "Hello, " + name + "!",
})
})

// 启动服务器
router.Run(":8080")

}
分类: 从问题出发深入理解Gin框架

相关文章
|
7月前
|
应用服务中间件 nginx
分布式限流
分布式限流
58 1
|
5月前
|
监控 算法 Java
高并发架构设计三大利器:缓存、限流和降级问题之配置Sentinel的流量控制规则问题如何解决
高并发架构设计三大利器:缓存、限流和降级问题之配置Sentinel的流量控制规则问题如何解决
|
7月前
|
存储 数据库 Nacos
微服务限流Sentinel讲解(五)
微服务限流Sentinel讲解(五)
133 0
|
Cloud Native 算法 安全
简单理解微服务限流、降级、熔断
简单理解微服务限流、降级、熔断
175 0
|
设计模式 监控 算法
高可用三大利器 — 熔断、限流和降级
在武侠世界里,“利器”通常指的是武器中的上乘、出色之物;武器对于武者的重要性不言而喻,拥有一把优秀的武器可以让武者在战斗中更加得心应手,威力更强。在分布式系统追求高可用的背景下,熔断、限流和降级这三个重要的策略可以称得上三大利器。降级和熔断是不是一回事?限流 与 降级呢?
230 2
|
算法 NoSQL Java
单机限流和分布式应用限流
单机限流和分布式应用限流
354 0
|
应用服务中间件 Sentinel 微服务
微服务限流Sentinel讲解(一)
微服务限流Sentinel讲解(一)
223 0
微服务限流Sentinel讲解(一)
|
SQL JavaScript Dubbo
浅谈微服务中限流熔断降级的方法论
易波动或者对波动比较敏感;容易影响整体的;不能预测上游行为,或者不能预测下游行为,依赖的上下游有不可预测的行为体。要不要做熔断降级的核心点在于是否可控,有没有不可控因素。
236 0
|
Java 应用服务中间件 Sentinel
微服务限流Sentinel讲解(四)
微服务限流Sentinel讲解(四)
242 0
微服务限流Sentinel讲解(四)
|
Sentinel 微服务
微服务限流Sentinel讲解(三)
微服务限流Sentinel讲解(三)
240 0
微服务限流Sentinel讲解(三)