当前位置: 首页 > news >正文

go语言后端开发学习(七)——如何在gin框架中集成限流中间件

一.什么是限流

限流又称为流量控制(流控),通常是指限制到达系统的并发请求数。
我们生活中也会经常遇到限流的场景,比如:某景区限制每日进入景区的游客数量为8万人;沙河地铁站早高峰通过站外排队逐一放行的方式限制同一时间进入车站的旅客数量等。
限流虽然会影响部分用户的使用体验,但是却能在一定程度上报障系统的稳定性,不至于崩溃(大家都没了用户体验)。
而互联网上类似需要限流的业务场景也有很多,比如电商系统的秒杀、微博上突发热点新闻、双十一购物节、12306抢票等等。这些场景下的用户请求量通常会激增,远远超过平时正常的请求量,此时如果不加任何限制很容易就会将后端服务打垮,影响服务的稳定性。
此外,一些厂商公开的API服务通常也会限制用户的请求次数,比如百度地图开放平台等会根据用户的付费情况来限制用户的请求数等。

二.常见的限流算法

2.1 漏桶算法

2.1.1 漏桶算法的原理

漏桶法的原理比较简单,假设我们有一个水桶按固定的速率向下方滴落一滴水,无论有多少请求,请求的速率有多大,都按照固定的速率流出,对应到系统中就是按照固定的速率处理请求。原理图如下:
在这里插入图片描述

漏桶法的关键点在于漏桶始终按照固定的速率运行,但是它并不能很好的处理有大量突发请求的场景,毕竟在某些场景下我们可能需要提高系统的处理效率,而不是一味的按照固定速率处理请求。

关于漏桶算法,在开发中我们可以使用三方的开源框架,uber团队有一个开源的github.com/uber-go/ratelimit库,下面网站是由漏桶算法集成以下如何简单的在gin中集成一个由漏桶算法实现的限流中间(这里我用的是我尝试自己编写的一个漏桶算法代码,大家可以选择自己写也可以选择使用上面的开源框架):

//开源框架版
package mainimport ("time""github.com/gin-gonic/gin""go.uber.org/ratelimit"
)func pong(c *gin.Context) {c.JSON(200, gin.H{"code":    200,"message": "pong",})}func LimitHandler() gin.HandlerFunc {return func(c *gin.Context) {r := ratelimit.New(1)  //每秒1个请求//每次滴水允许通过的请求数量// 限流if r.Take().Sub(time.Now()) > 0 {c.JSON(200, gin.H{"code":    429,"message": "Server busy",})c.Abort()}}
}func main() {r := gin.Default()r.Use(LimitHandler()){r.GET("/ping", pong)}r.Run(":8080")
}
//自己实现版//main.go
package mainimport ("awesomeProject1/limit""time""github.com/gin-gonic/gin""go.uber.org/ratelimit"
)func pong(c *gin.Context) {c.JSON(200, gin.H{"code":    200,"message": "pong",})}func main() {r := gin.Default()r.Use(limit.LimitMiddleware()){}r.Run(":8080")
}//limit.go
package limitimport ("sync""time""github.com/gin-gonic/gin"
)type Bucket struct {sync.MutexlastAccess  time.Timerequests    int64         // 当前已经接收请求次数MaxRequests int64         // 最大可接受请求次数interval    time.Duration // 时间间隔
}func NewBucket(maxRequests int64, interval time.Duration) *Bucket {return &Bucket{lastAccess:  time.Now(),requests:    0,MaxRequests: maxRequests,interval:    interval,}
}func (b *Bucket) Allow() bool {b.Lock() //加锁defer b.Unlock()now := time.Now()if now.Sub(b.lastAccess) > b.interval {b.requests = 0b.lastAccess = now}if b.requests < b.MaxRequests {b.requests++return true}return false
}func LimitMiddleware() gin.HandlerFunc {bucket := NewBucket(1, 10*time.Second)return func(c *gin.Context) {if !bucket.Allow() {c.JSON(200, gin.H{"code":    429,"message": "Server busy",})c.Abort()}c.Next()}
}

2.2 令牌桶算法

2.2 令牌桶算法的原理

令牌桶其实和漏桶的原理类似,令牌桶会按固定的速率往桶里放入令牌,并且只要能从桶里取出令牌就能通过,令牌桶支持突发流量的快速处理。原理图如下:
在这里插入图片描述
当我们在令牌桶里面取不到令牌时我们就会选择拒绝该次请求。

2.2.2 基于令牌桶实现的限流中间件

和上面的漏桶限流一样,这里有关令牌桶的限流博主还是给出两个版本,一个是开源第三方库,同时博主也会写一个自己实现的限流中间件供大家参考:

  • 首先是第三方库,这里我们可以考虑使用github.com/juju/ratelimit这一第三方库,下面我们来看一下如何基于这一第三方库封装出外面的限流中间件:
//     filepath:/limit/limiter
package limitimport ("time""github.com/gin-gonic/gin""github.com/juju/ratelimit"
)// 考虑到我们可能会对不同的请求做不同的限流,因此需要一个通用的实现接口
type LimiterInterface interface {Key(c *gin.Context) string                              //基于context实现获取对应限流器键值对GetBucket(key string) (*ratelimit.Bucket, bool)         // 获取限流器AddBuckets(rules ...LimiterBucketRule) LimiterInterface // 添加限流器规则
}type Limiter struct{  // 限流器(用来记录不同接口对应的不同限流策略)LimiterBuckets map[string]*ratelimit.Bucket 
}type LimiterBucketRule struct {  // 限流器规则Key          string        //FillInterval time.Duration // 时间间隔Capacity     int64         // 容量Quantum      int64         // 每次放置的令牌量
}//  接口的具体实现   filepath:/limit/method_limiter.go
package limitimport ("strings""github.com/gin-gonic/gin""github.com/juju/ratelimit"
)type MethodLimiter struct {*Limiter
}func NewMethodLimiter() LimiterInterface {l := &Limiter{LimiterBuckets: make(map[string]*ratelimit.Bucket)}return &MethodLimiter{Limiter: l}
}func (l MethodLimiter) Key(c *gin.Context) string {url := c.Request.RequestURIindex := strings.Index(url, "?")if index != -1 {url = url[:index]}return url
}func (l MethodLimiter) GetBucket(key string) (*ratelimit.Bucket, bool) {bucket, ok := l.LimiterBuckets[key]return bucket, ok
}func (l MethodLimiter) AddBuckets(rules ...LimiterBucketRule) LimiterInterface {for _, rule := range rules {if bucket, ok := l.LimiterBuckets[rule.Key]; !ok {bucket = ratelimit.NewBucketWithQuantum(rule.FillInterval, rule.Capacity, rule.Quantum)l.LimiterBuckets[rule.Key] = bucket}}return l
}// 在gin框架中集成限流中间件        filepath: /middleware/limiter.go
package middlewareimport ("awesomeProject1/limit""fmt""github.com/gin-gonic/gin"
)func LimitHandler(l limit.LimiterInterface) gin.HandlerFunc {return func(c *gin.Context) {key := l.Key(c)if bucket, ok := l.GetBucket(key); ok {count := bucket.TakeAvailable(1)fmt.Println("key", key, "count", count)if count == 0 {c.JSON(429, gin.H{"code": 429,"msg":  "too many request",})c.Abort()}}c.Next()}
}//测试样例 main.go
package mainimport ("awesomeProject1/limit""awesomeProject1/middleware""time""github.com/gin-gonic/gin"
)func pong(c *gin.Context) {c.JSON(200, gin.H{"code":    200,"message": "pong",})}func main() {r := gin.Default()limiter := limit.NewMethodLimiter()limiter.AddBuckets(limit.LimiterBucketRule{Key:          "/ping",FillInterval: 10 * time.Second,Capacity:     1,Quantum:      1,},)r.Use(middleware.LimitHandler(limiter)){r.GET("/ping", pong)}r.Run(":8080")
}

大家可以自己测试一下

结语

上面就是一些常见的限流策略,虽然说现在限流策略已经不再是单体架构而是迈向分布式,但是万变不离其宗,主要还是基于上面所说的策略进行拓展

参考文章:
李文周博客——常用限流策略——漏桶与令牌桶介绍

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 系统编程-初识MCU
  • 滚雪球学SpringCloud[2.2]:Consul与Zookeeper服务注册
  • 【AI大模型】Kimi API大模型接口实现
  • 【python计算机视觉编程——9.图像分割】
  • 从头开始学MyBatis—02基于xml和注解分别实现的增删改查
  • C++——⼆叉搜索树
  • eMule firewall config - iptables forward rules
  • 【Kubernetes】常见面试题汇总(十二)
  • 使用Ubuntu耳机输出正弦波信号
  • 首次在rasa中使用form的个人小结
  • stm32 W25Q数据存储
  • C语言的结构体类型
  • Rust Windows下编译 静态链接VCRuntime140.dll
  • 华为 HCIP 认证费用和报名资格
  • 【5G QoS】详解5G QoS端到端工作机制
  • 【划重点】MySQL技术内幕:InnoDB存储引擎
  • 【刷算法】从上往下打印二叉树
  • - C#编程大幅提高OUTLOOK的邮件搜索能力!
  • GraphQL学习过程应该是这样的
  • Java 11 发布计划来了,已确定 3个 新特性!!
  • JavaScript 奇技淫巧
  • Python实现BT种子转化为磁力链接【实战】
  • zookeeper系列(七)实战分布式命名服务
  • 百度地图API标注+时间轴组件
  • 回顾 Swift 多平台移植进度 #2
  • 理清楚Vue的结构
  • 前端路由实现-history
  • 浅谈JavaScript的面向对象和它的封装、继承、多态
  • 扫描识别控件Dynamic Web TWAIN v12.2发布,改进SSL证书
  • 它承受着该等级不该有的简单, leetcode 564 寻找最近的回文数
  • 没有任何编程基础可以直接学习python语言吗?学会后能够做什么? ...
  • ​人工智能书单(数学基础篇)
  • ## 1.3.Git命令
  • $.ajax()参数及用法
  • (AngularJS)Angular 控制器之间通信初探
  • (LeetCode) T14. Longest Common Prefix
  • (八)c52学习之旅-中断实验
  • (待修改)PyG安装步骤
  • (简单有案例)前端实现主题切换、动态换肤的两种简单方式
  • (免费领源码)Java#ssm#MySQL 创意商城03663-计算机毕业设计项目选题推荐
  • (转)Linq学习笔记
  • (转)如何上传第三方jar包至Maven私服让maven项目可以使用第三方jar包
  • (转载)微软数据挖掘算法:Microsoft 时序算法(5)
  • .NET “底层”异步编程模式——异步编程模型(Asynchronous Programming Model,APM)...
  • .net core IResultFilter 的 OnResultExecuted和OnResultExecuting的区别
  • .net 前台table如何加一列下拉框_如何用Word编辑参考文献
  • .Net环境下的缓存技术介绍
  • .Net开发笔记(二十)创建一个需要授权的第三方组件
  • /etc/apt/sources.list 和 /etc/apt/sources.list.d
  • @Autowired @Resource @Qualifier的区别
  • @PostConstruct 注解的方法用于资源的初始化
  • @Transactional类内部访问失效原因详解
  • @软考考生,这份软考高分攻略你须知道
  • [AIGC] HashMap的扩容与缩容:动态调整容量以提高性能
  • [AIGC] 使用Curl进行网络请求的常见用法