gin中使用限流中间件

限流又称为流量控制(流控),通常是指限制到达系统的并发请求数,本文列举了常见的限流策略,并以gin框架为例演示了如何为项目添加限流组件。

限流

限流又称为流量控制(流控),通常是指限制到达系统的并发请求数。

我们生活中也会经常遇到限流的场景,比如:某景区限制每日进入景区的游客数量为8万人;沙河地铁站早高峰通过站外排队逐一放行的方式限制同一时间进入车站的旅客数量等。

限流虽然会影响部分用户的使用体验,但是却能在一定程度上报障系统的稳定性,不至于崩溃(大家都没了用户体验)。

而互联网上类似需要限流的业务场景也有很多,比如电商系统的秒杀、微博上突发热点新闻、双十一购物节、12306抢票等等。这些场景下的用户请求量通常会激增,远远超过平时正常的请求量,此时如果不加任何限制很容易就会将后端服务打垮,影响服务的稳定性。

此外,一些厂商公开的API服务通常也会限制用户的请求次数,比如百度地图开放平台等会根据用户的付费情况来限制用户的请求数等。

百度地图开放平台API调用策略

常用的限流策略

漏桶

漏桶法限流很好理解,假设我们有一个水桶按固定的速率向下方滴落一滴水,无论有多少请求,请求的速率有多大,都按照固定的速率流出,对应到系统中就是按照固定的速率处理请求。

漏桶算法原理

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

关于漏桶的实现,uber团队有一个开源的github.com/uber-go/ratelimit库。 这个库的使用方法比较简单,Take() 方法会返回漏桶下一次滴水的时间。

import (
	"fmt"
	"time"

	"go.uber.org/ratelimit"
)

func main() {
    rl := ratelimit.New(100) // per second

    prev := time.Now()
    for i := 0; i < 10; i++ {
        now := rl.Take()
        fmt.Println(i, now.Sub(prev))
        prev = now
    }

    // Output:
    // 0 0
    // 1 10ms
    // 2 10ms
    // 3 10ms
    // 4 10ms
    // 5 10ms
    // 6 10ms
    // 7 10ms
    // 8 10ms
    // 9 10ms
}

它的源码实现也比较简单,这里大致说一下关键的地方,有兴趣的同学可以自己去看一下完整的源码。

限制器是一个接口类型,其要求实现一个Take()方法:

type Limiter interface {
	// Take方法应该阻塞已确保满足 RPS
	Take() time.Time
}

实现限制器接口的结构体定义如下,这里可以重点留意下maxSlack字段,它在后面的Take()方法中的处理。

type limiter struct {
	sync.Mutex                // 锁
	last       time.Time      // 上一次的时刻
	sleepFor   time.Duration  // 需要等待的时间
	perRequest time.Duration  // 每次的时间间隔
	maxSlack   time.Duration  // 最大的富余量
	clock      Clock          // 时钟
}

limiter结构体实现Limiter接口的Take()方法内容如下:

// Take 会阻塞确保两次请求之间的时间走完
// Take 调用平均数为 time.Second/rate.
func (t *limiter) Take() time.Time {
	t.Lock()
	defer t.Unlock()

	now := t.clock.Now()

	// 如果是第一次请求就直接放行
	if t.last.IsZero() {
		t.last = now
		return t.last
	}

	// sleepFor 根据 perRequest 和上一次请求的时刻计算应该sleep的时间
	// 由于每次请求间隔的时间可能会超过perRequest, 所以这个数字可能为负数,并在多个请求之间累加
	t.sleepFor += t.perRequest - now.Sub(t.last)

	// 我们不应该让sleepFor负的太多,因为这意味着一个服务在短时间内慢了很多随后会得到更高的RPS。
	if t.sleepFor < t.maxSlack {
		t.sleepFor = t.maxSlack
	}

	// 如果 sleepFor 是正值那么就 sleep
	if t.sleepFor > 0 {
		t.clock.Sleep(t.sleepFor)
		t.last = now.Add(t.sleepFor)
		t.sleepFor = 0
	} else {
		t.last = now
	}

	return t.last
}

上面的代码根据记录每次请求的间隔时间和上一次请求的时刻来计算当次请求需要阻塞的时间——sleepFor,这里需要留意的是sleepFor的值可能为负,在经过间隔时间长的两次访问之后会导致随后大量的请求被放行,所以代码中针对这个场景有专门的优化处理。创建限制器的New()函数中会为maxSlack设置初始值,也可以通过WithoutSlack这个Option取消这个默认值。

func New(rate int, opts ...Option) Limiter {
	l := &limiter{
		perRequest: time.Second / time.Duration(rate),
		maxSlack:   -10 * time.Second / time.Duration(rate),
	}
	for _, opt := range opts {
		opt(l)
	}
	if l.clock == nil {
		l.clock = clock.New()
	}
	return l
}

令牌桶

令牌桶其实和漏桶的原理类似,令牌桶按固定的速率往桶里放入令牌,并且只要能从桶里取出令牌就能通过,令牌桶支持突发流量的快速处理。

令牌桶原理

对于从桶里取不到令牌的场景,我们可以选择等待也可以直接拒绝并返回。

对于令牌桶的Go语言实现,大家可以参照github.com/juju/ratelimit库。这个库支持多种令牌桶模式,并且使用起来也比较简单。

创建令牌桶的方法:

// 创建指定填充速率和容量大小的令牌桶
func NewBucket(fillInterval time.Duration, capacity int64) *Bucket
// 创建指定填充速率、容量大小和每次填充的令牌数的令牌桶
func NewBucketWithQuantum(fillInterval time.Duration, capacity, quantum int64) *Bucket
// 创建填充速度为指定速率和容量大小的令牌桶
// NewBucketWithRate(0.1, 200) 表示每秒填充20个令牌
func NewBucketWithRate(rate float64, capacity int64) *Bucket

取出令牌的方法如下:

// 取token(非阻塞)
func (tb *Bucket) Take(count int64) time.Duration
func (tb *Bucket) TakeAvailable(count int64) int64

// 最多等maxWait时间取token
func (tb *Bucket) TakeMaxDuration(count int64, maxWait time.Duration) (time.Duration, bool)

// 取token(阻塞)
func (tb *Bucket) Wait(count int64)
func (tb *Bucket) WaitMaxDuration(count int64, maxWait time.Duration) bool

虽说是令牌桶,但是我们没有必要真的去生成令牌放到桶里,我们只需要每次来取令牌的时候计算一下,当前是否有足够的令牌就可以了,具体的计算方式可以总结为下面的公式:

当前令牌数 = 上一次剩余的令牌数 + (本次取令牌的时刻-上一次取令牌的时刻)/放置令牌的时间间隔 * 每次放置的令牌数

github.com/juju/ratelimit这个库中关于令牌数计算的源代码如下:

func (tb *Bucket) currentTick(now time.Time) int64 {
	return int64(now.Sub(tb.startTime) / tb.fillInterval)
}
func (tb *Bucket) adjustavailableTokens(tick int64) {
	if tb.availableTokens >= tb.capacity {
		return
	}
	tb.availableTokens += (tick - tb.latestTick) * tb.quantum
	if tb.availableTokens > tb.capacity {
		tb.availableTokens = tb.capacity
	}
	tb.latestTick = tick
	return
}

获取令牌的TakeAvailable()函数关键部分的源代码如下:

func (tb *Bucket) takeAvailable(now time.Time, count int64) int64 {
	if count <= 0 {
		return 0
	}
	tb.adjustavailableTokens(tb.currentTick(now))
	if tb.availableTokens <= 0 {
		return 0
	}
	if count > tb.availableTokens {
		count = tb.availableTokens
	}
	tb.availableTokens -= count
	return count
}

大家从代码中也可以看到其实令牌桶的实现并没有很复杂。

gin框架中使用限流中间件

在gin框架构建的项目中,我们可以将限流组件定义成中间件。

这里使用令牌桶作为限流策略,编写一个限流中间件如下:

func RateLimitMiddleware(fillInterval time.Duration, cap int64) func(c *gin.Context) {
	bucket := ratelimit.NewBucket(fillInterval, cap)
	return func(c *gin.Context) {
		// 如果取不到令牌就中断本次请求返回 rate limit...
		if bucket.TakeAvailable(1) < 1 {
			c.String(http.StatusOK, "rate limit...")
			c.Abort()
			return
		}
		c.Next()
	}
}

对于该限流中间件的注册位置,我们可以按照不同的限流策略将其注册到不同的位置,例如:

  1. 如果要对全站限流就可以注册成全局的中间件。
  2. 如果是某一组路由需要限流,那么就只需将该限流中间件注册到对应的路由组即可。

参考文章:

https://www.fansimao.com/937511.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/346398.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ASP.NET Core WebAPI从HTTPS调整为HTTP启动

使用VS2022创建WebAPI项目时&#xff0c;默认勾选“配置HTTPS(H)”&#xff0c;这样启动WebAPI时以https方式启动。   如果要从HTTPS调整为HTTP启动&#xff0c;需要修改项目中以下几处&#xff0c;首先是Program.cs中删除app.UseHttpsRedirection()语句&#xff0c;删除后…

thymeleaf常用语法大全

有时候需要借鉴别人的代码&#xff0c;发现一个相似的功能点&#xff0c;但是自己的是html页面别人的是jsp页面&#xff0c;那如果不了解thymeleaf的话还是要费点功夫的。 什么是thymeleaf&#xff0c;通俗点&#xff0c;jsp中的${},以及jstl中的if标签什么的都不能用&#xf…

文件包含技术总结

开发人员一般会把重复使用的函数写到单个文件中&#xff0c;需要使用某个函数时直接调用此文件&#xff0c;而无需再次编写&#xff0c;这中文件调用的过程一般被称为文件包含。 allow_url_fopen On&#xff08;是否允许打开远程文件&#xff09; allow_url_include On&…

编译安装Nginx和使用五种算法实现Nginx反向代理负载均衡

目录 Ubuntu中安装Nginx 概念介绍 负载均衡 几种负载均衡算法 反向代理 环境规划 配置反向代理 加权负载均衡&#xff08;Weighted Load Balancing&#xff09; 轮询&#xff08;Round Robin&#xff09; IP 哈希&#xff08;IP Hash&#xff09; 最少连接&#xff…

多元跨界、戮力谐老!2024深圳国际户外运动展览会再创运动生活新方式

COSP Shenzhen 2024国际户外运动用品与时尚展 2024年3.14-16日 深圳会展中心(福田馆&#xff09; COSP Shanghai 2024国际户外运动用品与时尚展 2024年9.05-07日 上海世博展览馆&#xff08;浦东&#xff09; 展会概述&#xff1a; 作为国内最具影响力的户外运动展会之一…

聚观早报 | 马云大幅增持阿里股票;苹果下调自动驾驶目标

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。 整理丨Cutie 1月25日消息 马云大幅增持阿里股票 苹果下调自动驾驶目标 华硕天选5 Pro锐龙版正式发布 Redmi K70至尊版细节曝光…

《如何画好架构图》学习笔记

看了一堂《如何画好架构图》的公开课&#xff0c;结合网上的资料与经验做一些思考总结。文中的例子和图片大多是从课程中摘录的。 1. 4R架构定义 4R架构定义其实是软件架构定义经过归纳提炼后的简称。 软件架构定义&#xff1a;软件架构是指软件系统的顶层&#xff08;Rank&am…

基于cubeMX的正点原子miniSTM32对W25Q64的存储使用

一、实现目标 使用cubeMX建立项目工程&#xff0c;结合正点原子提供的hal库对W25Q64闪存调用的例程&#xff0c;实现W25Q64的读写。 二、实现过程 1、首先建立cubeMX工程&#xff0c;其他项设置不再叙述&#xff0c;只看连接W25Q64的SPI设置&#xff0c;这里使用SPI1&#xf…

01.领域驱动设计:微服务设计为什么要选择DDD学习总结

目录 1、前言 2、软件架构模式的演进 3、微服务设计和拆分的困境 4、为什么 DDD适合微服务 5、DDD与微服务的关系 6、总结 1、前言 我们知道&#xff0c;微服务设计过程中往往会面临边界如何划定的问题&#xff0c;不同的人会根据自己对微服务的理 解而拆分出不同的微服…

AI大模型中的Bert

1.全方位上下文理解&#xff1a;与以前的模型&#xff08;例如GPT&#xff09;相比&#xff0c;BERT能够双向理解上下文&#xff0c;即同时考虑一个词 的左边和右边的上下文。这种全方位的上下文理解使得BERT能够更好地理解语言&#xff0c;特别是在理解词义、 消歧等复杂任务上…

苹果AI新动向:隐秘收购与人才招募揭示其下一代AI技术布局

AI圈的隐形“大佬” 苹果公司于2023年7月被传出正在积极涉足生成式AI领域&#xff0c;据传正在开发名为“AJAX”的大型语言模型。他们甚至为员工内部开发了类似 ChatGPT的聊天机器人&#xff0c;可能被称为“Apple GPT”。这一创新工作由苹果公司的机器学习与AI部门主管John G…

视频汇聚/云存储平台EasyCVR级联上级播放后一直发流是什么原因?

可视化云监控平台/安防视频监控系统EasyCVR视频综合管理平台&#xff0c;采用了开放式的网络结构&#xff0c;可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联、磁盘阵列存储、视频集中存储、云存储等丰富的视频能力&#xff0c;同时…

Oracle篇—分区表的管理(第二篇,总共五篇)

☘️博主介绍☘️&#xff1a; ✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ ✌✌️擅长Oracle、MySQL、SQLserver、Linux&#xff0c;也在积极的扩展IT方向的其他知识面✌✌️ ❣️❣️❣️大佬们都喜欢静静的看文章&#xff0c;并且也会默默的点赞收藏加关注❣…

【LeetCode每日一题】2865. 美丽塔 I

2024-1-24 文章目录 [2865. 美丽塔 I](https://leetcode.cn/problems/beautiful-towers-i/) 2865. 美丽塔 I 初始化变量 ans 为0&#xff0c;用于记录最大的和值。获取整数列表的长度&#xff0c;保存到变量 n 中。使用一个循环遍历列表中的每个位置&#xff0c;从0到n-1。在循…

如何使用phpStudy软件测试本地PHP及环境搭建

各位同学朋友们大家好&#xff01;我是咕噜铁蛋&#xff01;我们经常需要在本地进行PHP代码的开发和测试。而phpStudy作为一个集成了Apache、MySQL和PHP的软件套装&#xff0c;提供了方便快捷的环境搭建和测试工具。今天铁蛋为大家详细介绍如何使用phpStudy来测试本地PHP及环境…

智能家居20年,从「动手」到「用脑」

【潮汐商业评论/原创】 正在装修新家的Carro最近陷入了纠结之中&#xff0c;“还没想好要怎么装一套完整的智能家居&#xff0c;家里的基装就已经开始了。” 事实上&#xff0c;Carro对智能家居也不了解&#xff0c;并不知道该如何下手&#xff0c;心想“要是能一次性设计好就…

WIFI电路原理时序检修思路

uart是串口&#xff0c;bt是蓝牙&#xff0c;hsic是高速接口。pcm是音频接口。时序图的第五步是发出就绪信号&#xff0c;然后第六步与门发出就绪信号。 wifi芯片是u8_rf。 特别说明&#xff1a;short表示短接。xw表示实际是看不到物体的&#xff0c;是直接相连的。 找信号50_…

【JavaWeb】MVC架构模式

文章目录 MVC是什么&#xff1f;一、M &#xff1a;Model 模型层二、V&#xff1a;View 视图层三、C&#xff1a;Controller 控制层四、非前后端分离MVC五、前后端分离MVC总结 MVC是什么&#xff1f; MVC&#xff08;Model View Controller&#xff09;是软件工程中的一种**软件…

音乐证书通过率发布,市场对持有者需求旺盛

音乐证书的考试难度备受关注&#xff0c;通过率终于揭晓。据官方公布的数据&#xff0c;该证书的通过率相对较低&#xff0c;需要考生在音乐技能和表现方面有出色的表现。然而&#xff0c;持有音乐证书的人才在市场上需求旺盛&#xff0c;各种音乐机构和企业对其表现出强烈兴趣…

深入理解stressapptest

文章目录 一、概述二、安装2.1、源码编译安装2.2、命令行安装2.3、安装确认三、重要参数详解3.1、查询支持的参数3.2、参数说明 四、实例4.1、随机测试&#xff08;默认模式&#xff09;4.2、循环测试4.2、全内存测试 团队博客: 汽车电子社区 一、概述 stressapptest是一款免费…