【Gin-Web】Bluebell社区项目梳理6:限流策略-漏桶与令牌桶
本文目录
- 一、限流
- 二、漏桶
- 三、令牌桶算法
- 四、Gin框架中实现令牌桶限流
一、限流
限流又称为流量控制,也就是流控,通常是指限制到达系统的并发请求数。
限流虽然会影响部分用户的使用体验,但是能一定程度上保证系统的稳定性,不至于崩溃。
常见的各种厂商的公开API服务通常也会限制用户的请求次数,比如百度地图的API来限制请求数等。
二、漏桶
漏桶是一种比较常见的限流策略。
一句话来概括漏洞的核心就是:数据以任意速率进入漏桶,但是漏桶以固定速率(通常是预先设定的速率)将数据输出。
有点像削峰填谷,但是它缺点也很明显,就是不能很好处理有大量突发请求的场景。
import ("fmt""time""go.uber.org/ratelimit"
)func main() {rl := ratelimit.New(100) // per secondprev := 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
}
上面你的代码是uber团队开源的漏桶库ratelimit,这个库比较简单。
ratelimit.New(100) 创建了一个速率限制器(ratelimit.Limiter),限制频率为每秒 100 次。这里的 100 表示每秒最多可以获取 100 次令牌(tokens),即每秒最多可以执行 100 次操作。
prev 是一个 time.Time 类型的变量,用于记录上一次操作的时间。初始值为当前时间。
rl.Take()这是 ratelimit.Limiter 的一个方法,用于获取一个令牌。如果当前时间距离上一次获取令牌的时间小于 1/100 秒(因为每秒 100 次),Take() 方法会阻塞,直到下一个令牌可用。now 是获取令牌时的时间点(time.Time 类型)。
now.Sub(prev) 计算当前时间点 now 和上一次时间点 prev 之间的时间差(返回 time.Duration 类型)。这个时间差表示两次操作之间的时间间隔。
所以这段代码的目的是通过 ratelimit.New(100) 创建一个每秒最多执行 100 次操作的速率限制器,并在循环中模拟 10 次操作。每次操作之间的时间间隔会被打印出来,用于观察速率限制器是否按预期工作。
我们来看看这个限流器的实现源码。
限制器是一个接口类型,其要求实现一个Take()方法:
type Limiter interface {// Take方法应该阻塞已确保满足 RPSTake() 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 = nowreturn 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 是正值那么就 sleepif 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
}
三、令牌桶算法
令牌桶按固定的速率往桶里放入令牌,并且只要能从桶里取出令牌就能通过,令牌桶支持突发流量的快速处理。
如果放令牌的速率很快,那么令牌桶里边有很多令牌了,这个时候当有大量的客户端请求过来了,那么就可以直接取出令牌处理请求。
也就是令牌以固定的速率(例如每秒生成若干个令牌)被添加到桶中,桶有一个最大容量,当桶满时,多余的令牌会被丢弃。
次发送数据(如网络请求或数据包)时,需要从桶中取出一定数量的令牌。如果桶中有足够的令牌,则允许发送数据,并从桶中扣除相应的令牌;如果令牌不足,则请求被拒绝或排队等待。
由于桶中的令牌可以积累,因此在短时间内允许突发流量(bursty traffic),只要平均速率符合限制。
对于从桶里取不到令牌的场景,我们可以选择等待也可以直接拒绝并返回。这个需要根据业务场景来处理,如果业务场景不同,那么相对应的处理也就需要不一样。
可以参照github上的开源库: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
(注意这个令牌桶跟JWT算法中的Token并不是一个概念。)
虽说是令牌桶但没有必要真的去生成令牌放到桶里,我们只需要每次来取令牌的时候计算一下,当前是否有足够的令牌就可以了,具体的计算方式可以总结为下面的公式:
当前令牌数 = 上一次剩余的令牌数 + (本次取令牌的时刻-上一次取令牌的时刻)/放置令牌的时间间隔 * 每次放置的令牌数
四、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) == 0 {c.String(http.StatusOK, "rate limit...")c.Abort()return}// 取到令牌就放行c.Next()}
}
cap是填充速率,比如一秒钟填一个等。
然后我们在路由中生成即可,这样全网站就限速了。

相关文章:
【Gin-Web】Bluebell社区项目梳理6:限流策略-漏桶与令牌桶
本文目录 一、限流二、漏桶三、令牌桶算法四、Gin框架中实现令牌桶限流 一、限流 限流又称为流量控制,也就是流控,通常是指限制到达系统的并发请求数。 限流虽然会影响部分用户的使用体验,但是能一定程度上保证系统的稳定性,不至…...
51单片机-AT24CXX存储器工作原理
1、AT24CXX存储器工作原理 1.1、特点: 与400KHz,I2C总线兼容1.8到6.0伏工作电压范围低功耗CMOS技术写保护功能当WP为高电平时进入写保护状态页写缓冲器自定时擦写周期100万次编程/擦除周期可保存数据100年8脚DIP SOIC或TSSOP封装温度范围商业级和工业级…...
突破性能极限:DeepSeek开源FlashMLA解码内核技术解析
引言:大模型时代的推理加速革命 在生成式AI大行其道的今天,如何提升大语言模型的推理效率已成为行业焦点。DeepSeek团队最新开源的FlashMLA项目凭借其惊人的性能表现引发关注——在H800 GPU上实现580 TFLOPS计算性能,这正是大模型推理优化的…...
点击修改按钮图片显示有问题
问题可能出在表单数据的初始化上。在 ave-form.vue 中,我们需要处理一下从后端返回的图片数据,因为它们可能是 JSON 字符串格式。 vue:src/views/tools/fake-strategy/components/ave-form.vue// ... existing code ...Watch(value)watchValue(v: any) …...
[AI]从零开始的树莓派运行DeepSeek模型教程
一、前言 在前面的教程中,教了大家如何在windows中使用llama.cpp来运行DeepSeek模型。根据前面的教程中,我们也了解到了,我们只需要编译好llama.cpp就可以运行DeepSeek以及类似的LLM模型。那么本次教程就来教大家如何使用树莓派来运行大模型。…...
2024-2025 学年广东省职业院校技能大赛 “信息安全管理与评估”赛项 技能测试试卷(二)
2024-2025 学年广东省职业院校技能大赛 “信息安全管理与评估”赛项 技能测试试卷(二) 第一部分:网络平台搭建与设备安全防护任务书第二部分:网络安全事件响应、数字取证调查、应用程序安全任务书任务 1:应急响应&…...
Open WebUI本地部署教程
文章目录 1、系统环境配置2、源码下载2.1 github源码地址下载 3、环境启动3.1 后端环境3.2 前端环境 4、问题4.1 浏览器跨域问题4.2 all-MiniLM-L6-v2模型文件下载失败问题4.3 单独部署backend启动报错问题 1、系统环境配置 操作系统:windows/linux/macos Python版…...
Missing required prop: “maxlength“
背景: 封装一个使用功能相同使用频率较高的input公共组件作为子组件,大多数长度要求为200,且实时显示统计子数,部分input有输入提示。 代码实现如下: <template><el-input v-model"inputValue" t…...
dify本地部署
安装docker。 在官网安装docker。 如果遇到wsl报错,就使用 wsl --updata 进行更新。如果问题解决,进入docker应该是如下界面: 克隆 在自己创建的文件内使用 git clone gitgithub.com:langgenius/dify.git 或 git clone https://github.com…...
python学习一
学习网络安全为什么要学python? 1、在实际的渗透测试过程中,面对复杂多变的网络环境,当常用工 具不能满足实际需求的时候,往往需要对现有工具进行扩展,或者 编写符合我们要求的工具、自动化脚本,这个时候就需要具备一定 的编程能力。 2、python是一门编程语言经常用它…...
git branch
文章目录 1.简介2.格式3.选项4.示例参考文献 1.简介 git branch 用于管理分支,包括查看、创建、删除、重命名和关联。 git branch 是 Git 版本控制系统中用于管理分支的命令。分支是 Git 的核心功能之一,允许开发者在同一个代码库中并行开发不同的功能…...
算法-图-数据结构(邻接矩阵)-BFS广度优先遍历
邻接矩阵广度优先遍历(BFS)是一种用于遍历或搜索图的算法,以下是具体介绍: 1. 基本概念 图是一种非线性的数据结构,由顶点和边组成,可分为无向图、有向图、加权图、无权图等。邻接矩阵是表示图的一种数…...
数学建模之数学模型—2:非线性规划
文章目录 非线性规划基本概念与结论凸集与凸函数极值条件无约束条件的极值判断条件有约束条件的极值判断条件 无约束非线性规划一维搜索算法步骤示例特点代码模板 最速下降法算法详细步骤 代码实现示例最优步长的求解 黄金分割法斐波那契法牛顿法阻尼牛顿法模式搜索法Powell方法…...
unity学习51:所有UI的父物体:canvas画布
目录 1 下载资源 1.1 在window / Asset store下下载一套免费的UI资源 1.2 下载,导入import 1.3 导入后在 project / Asset下面可以看到 2 画布canvas,UI的父物体 2.1 创建canvas 2.1.1 画布的下面是 event system是UI相关的事件系统 2.2 canvas…...
ctfshow做题笔记—栈溢出—pwn57~pwn60
目录 前言 一、pwn57(先了解一下简单的64位shellcode吧) 二、pwn58 三、pwn59(64位 无限制) 四、pwn60(入门难度shellcode) 前言 往前写了几道题,与shellcode有关,关于shellc…...
数据结构 1-2 线性表的链式存储-链表
1 原理 顺序表的缺点: 插入和删除移动大量元素数组的大小不好控制占用一大段连续的存储空间,造成很多碎片 链表规避了上述顺序表缺点 逻辑上相邻的两个元素在物理位置上不相邻 头结点 L:头指针 头指针:链表中第一个结点的存储…...
ArcGIS Pro进行坡度与坡向分析
在地理信息系统中,坡度分析是一项至关重要的空间分析方法,旨在精确计算地表或地形的坡度,为地形特征识别、土地资源规划、环境保护、灾害预警等领域提供科学依据。本文将详细介绍如何利用ArcGIS Pro这一强大的地理信息系统软件,进…...
My first Android application
界面元素组成: 功能代码: /*实现功能:当输入内容后,欢迎文本发生相应改变,并清除掉文本域内容当未输入任何内容时,弹出提示文本以警告用户*/val greetingText findViewById<TextView>(R.id.printer)…...
ZLMediaKi集群设置
要在集群环境中部署 ZLMediaKit,您可以按照以下步骤进行操作。ZLMediaKit 是一个高性能的流媒体服务器,支持 RTMP、RTSP、HLS 等协议。以下是一个详细的集群部署方案: ### 1. 环境准备 - **服务器**:准备多台服务器,…...
Docker基础实践与应用举例
Docker 是一个轻量级容器化平台,通过将应用及其依赖打包到容器中,实现快速部署和环境一致性。以下是 Docker 的实践与应用场景举例,结合具体操作步骤: 一、基础实践 1. 快速启动一个容器 # 运行一个Nginx容器,映射宿…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...
GitHub 趋势日报 (2025年06月08日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...
涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
MySQL中【正则表达式】用法
MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现(两者等价),用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例: 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...
嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...
4. TypeScript 类型推断与类型组合
一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式,自动确定它们的类型。 这一特性减少了显式类型注解的需要,在保持类型安全的同时简化了代码。通过分析上下文和初始值,TypeSc…...
