Golang编写自定义IP限流中间件的方法详解

 更新时间:2023年09月18日 09:21:51   作者:小小小熊猫5  
这篇文章给大家详细的介绍了Golang编写自定义IP限流中间件的方法,文章通过代码实例介绍的非常详细,对大家的学习或工作有一定的帮助,需要的朋友可以参考下

基于令牌桶的限流算法

  • r:每秒钟向桶内放入 r 个令牌,即每隔 1/r秒放一个令牌
  • b:桶的最大容量是 b,桶满后试图再放入的令牌会被丢弃掉
  • 当有人请求 n 个令牌时,如果桶中的令牌数小于n,则请求放阻塞或直接放弃,否则顺利从桶中取走 n 个令牌
    • 当 b > 1 时,任意 1/r 秒内最多可以取走 b 个令牌
      • 限制很短的一个瞬间的一个最高的并发量,b=最高的并发量,r=最短的时间段
    • 当 b = 1 时,每秒钟最多可被取走 r 个令牌
      • 限制每秒钟的最高的QPS:b=1,r=最高的QPS
      • 限制每分钟的最高请求量:把每分钟的请求量/60=转换成1秒钟,赋给 r 就可以了
      • 限制每5分钟、每10分钟,同理

实现高并发限流(使用golang官方限流器)

Go代码

源码地址: GitHub-golang版本

middleware/rateLimiterMiddleware.go

package middleware
import (
	"net/http"
	"time"
	"github.com/gin-gonic/gin"
	"golang.org/x/time/rate"
)
var Limiter *rate.Limiter
// 定义一个中间件函数来进行限流
func RateLimiterMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		if !Limiter.AllowN(time.Now(), 1) {
			c.JSON(http.StatusTooManyRequests, gin.H{"message": "Rate limit exceeded"})
			// 设置休眠和业务时长一样,为了更好从日志出看出规则
			time.Sleep(50 * time.Millisecond)
			c.Abort()
			return
		}
		c.Next()
	}
}

main.go

func main() {
	r := gin.Default()
	// 创建一个限流器,每秒允许最多10个请求
	middleware.Limiter = rate.NewLimiter(rate.Limit(10), 1)
	// 使用限流中间件
	r.Use(middleware.RateLimiterMiddleware())
	r.GET("/api/resource", func(c *gin.Context) {
		time.Sleep(50 * time.Millisecond)
		c.JSON(http.StatusOK, gin.H{"message": "Resource accessed"})
	})
	r.Run(":8080")
}

测试记录

ab -t 1 -c 1 http://127.0.0.1:8080/api/resource

使用ab压力测试,并发量为1的(相当于串行),在1秒内不断发出请求(算下来,每个请求50ms,总共能发出20个请求)

结果预测:1秒内最多生成10个令牌,而总共有20个串行的请求,结果应该是1个成功(在50ms结束),1个失败(后50ms内还未有新的令牌生成),1个成功,1个失败。。。 结果输出(符合预期)

在这里插入图片描述

在这里插入图片描述

升级:根据每个IP地址进行限流

Go代码

源码地址: GitHub-golang版本

middleware/ipRateLimiterMiddleware.go

package middleware
import (
	"net/http"
	"sync"
	"time"
	"github.com/gin-gonic/gin"
	"golang.org/x/time/rate"
)
var IPLimiter *IPRateLimiter
func NewIPRateLimiter() *IPRateLimiter {
	return &IPRateLimiter{
		limiter: make(map[string]*rate.Limiter),
	}
}
type IPRateLimiter struct {
	mu      sync.Mutex
	limiter map[string]*rate.Limiter
}
func (i *IPRateLimiter) GetLimiter(ip string) *rate.Limiter {
	i.mu.Lock()
	defer i.mu.Unlock()
	limiter, exists := i.limiter[ip]
	if !exists {
		limiter = rate.NewLimiter(2, 5) // 每秒2个请求,桶容量为5
		i.limiter[ip] = limiter
	}
	return limiter
}
// 定义一个中间件函数来进行限流
func IPRateLimiterMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		ip := c.ClientIP()
		limiter := IPLimiter.GetLimiter(ip)
		if !limiter.Allow() {
			c.JSON(http.StatusTooManyRequests, gin.H{"message": "Rate limit exceeded"})
			// 设置休眠和业务时长一样,为了更好从日志出看出规则
			time.Sleep(50 * time.Millisecond)
			c.Abort()
			return
		}
		c.Next()
	}
}

main.go

func main() {
	r := gin.Default()
	// 创建IP限流器
	middleware.IPLimiter = middleware.NewIPRateLimiter()
	// 使用限流中间件
	r.Use(middleware.IPRateLimiterMiddleware())
	r.GET("/api/resource", func(c *gin.Context) {
		time.Sleep(50 * time.Millisecond)
		c.JSON(http.StatusOK, gin.H{"message": "Resource accessed"})
	})
	r.Run(":8080")
}

测试记录

ab -t 1 -c 1 http://127.0.0.1:8080/api/resource

使用ab压力测试,并发量为1的(相当于串行),在1秒内不断发出请求(算下来,每个请求50ms,总共能发出20个请求)

结果预测:1秒内最多生成2个令牌,桶容量为5 代表在1/2秒内的最大并发量是5,总共有20个串行的请求,结果应该是先成功5个(桶容量全使用成功), 之后剩余成功的是1个,其余全部失败

  • 当程序启动时,限流器初始化,桶是空的,没有令牌。
  • 在每秒的前两次令牌生成中,每次生成2个令牌,并放入桶中。
  • 在第3秒,生成的2个令牌中只有1个能够被放入桶中,因为桶已经满了。
  • 当使用ab发出请求时,前5个请求依次拿到了令牌并成功,每个请求耗时50ms。此时总耗时为250ms。
  • 第6个请求在300ms,第7个请求在350ms,第8个请求在400ms,第9个请求在450ms,第10个请求在500ms。
  • 因为限流器的速率是每秒2个请求,也就是每500ms生成一个令牌。 第500ms时新的令牌生成,所以第6-10请求期间,是没有令牌的,所以都请求失败,而11个请求时有新令牌生成,所以能成功。
  • 之后的请求以此类推,需要等待新令牌生成。

结果输出(符合预期)

在这里插入图片描述

在这里插入图片描述

以上就是Golang编写自定义IP限流中间件的方法详解的详细内容,更多关于Golang编写IP限流中间件的资料请关注脚本之家其它相关文章!

相关文章

  • golang 结构体初始化时赋值格式介绍

    golang 结构体初始化时赋值格式介绍

    这篇文章主要介绍了golang 结构体初始化时赋值格式介绍,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • 详解Go并发编程时如何避免发生竞态条件和数据竞争

    详解Go并发编程时如何避免发生竞态条件和数据竞争

    大家都知道,Go是一种支持并发编程的编程语言,但并发编程也是比较复杂和容易出错的。比如本篇分享的问题:竞态条件和数据竞争的问题
    2023-04-04
  • 细说Go语言中空结构体的奇妙用途

    细说Go语言中空结构体的奇妙用途

    Go语言中,我们可以定义空结构体,即没有任何成员变量的结构体,使用关键字 struct{} 来表示。这种结构体似乎没有任何用处,但实际上它在 Go 语言中的应用非常广泛,本文就来详解讲讲
    2023-05-05
  • Go 的入口函数和包初始化的使用

    Go 的入口函数和包初始化的使用

    本文主要介绍了Go 的入口函数和包初始化的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-05-05
  • 基于Go语言实现猜谜游戏

    基于Go语言实现猜谜游戏

    这篇文章主要为大家详细介绍了如何基于Go语言实现猜谜游戏,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习
    2023-09-09
  • Go 防止 goroutine 泄露的方法

    Go 防止 goroutine 泄露的方法

    Go 的并发模型与其他语言不同,虽说它简化了并发程序的开发难度,但如果不了解使用方法,常常会遇到 goroutine 泄露的问题。本篇主要从如何写出正确代码的角度来介绍如何防止 goroutine 的泄露,需要的朋友可以参考下
    2019-09-09
  • Go实现数据脱敏的方案设计

    Go实现数据脱敏的方案设计

    在一些常见的业务场景中可能涉及到用户的手机号,银行卡号等敏感数据,对于这部分的数据经常需要进行数据脱敏处理,就是将此部分数据隐私化,防止数据泄露,所以本文给大家介绍了Go实现数据脱敏的方案设计,需要的朋友可以参考下
    2024-05-05
  • Golang迭代如何在Go中循环数据结构使用详解

    Golang迭代如何在Go中循环数据结构使用详解

    这篇文章主要为大家介绍了Golang迭代之如何在Go中循环数据结构使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-10-10
  • Go代码检查的推荐工具及使用详解

    Go代码检查的推荐工具及使用详解

    这篇文章主要为大家介绍了Go代码检查的推荐工具及使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-07-07
  • Go结合JavaScript实现抓取网页中的图像链接

    Go结合JavaScript实现抓取网页中的图像链接

    这篇文章主要为大家详细介绍了Go语言如何结合JavaScript实现抓取网页中的图像链接,文中的示例代码讲解详细,有需要的小伙伴可以跟随小编一起学习一下
    2023-11-11

最新评论