封装一个细粒度的限流器
文章目录
- 原因
- 限流对象
- 限流后的做法
- 怎么确定限流阈值
- 观测业务性能数据
- 压测
- 借鉴链路上的其他服务
- 手动计算
- 四种静态限流算法
- 令牌桶
- 漏桶
- 固定窗口与滑动窗口
- 手写限流算法
- 令牌桶
- 漏桶
- 固定窗口
- 滑动窗口
- 分布式限流的具体实现
原因
尽管云原生网关里有统一入口的限流(根据ip、userID来控制),但是有的微服务需要有自己的限流策略(比如根据不同的算法任务、不同的子产品来限制),所以封装了一个限流器公共包,可以在多个微服务中复用这个功能。直接原因是有一次某个子功能流量激增,大量任务失败。
关键步骤:
- 实现限流策略,例如基于令牌桶或漏桶
- 配置和初始化,微服务启动时加载配置和初始化限流器
限流对象
针对ip限流,例如1s限制50个请求,考虑到公共ip的情况;
针对某个算法任务,不限制vip用户,普通用户1s限制创建10个创建任务的请求。
限流后的做法

目前的做法是限流了就直接拒绝,抛出错误提示,还有其他的做法:
- 同步阻塞等待一段时间。如果是偶发性地触发了限流,那么稍微阻塞等待一会儿,后面就有极大的概率能得到处理。比如说限流设置为一秒钟 100 个请求,恰好来了 101 个请求。多出来的一个请求只需要等一秒钟,下一秒钟就会被处理。但是要注意控制住超时,也就是说你不能让人无限期地等待下去。
- 同步转异步。这里我们又一次看到了这个手段,它是指如果一个请求没被限流,那就直接同步处理;而如果被限流了,那么这个请求就会被存储起来,等到业务低峰期的时候再处理。这个其实跟降级差不多。(TODO 研究到降级时再过来看一下)
- 调整负载均衡算法(用redis的话似乎跟负载均衡没关系,如果是在网关里做限流是可以调整负载均衡器的)。如果某个请求被限流了,那么就相当于告诉负载均衡器,应该尽可能少给这个节点发送请求。我在熔断里面给你讲过类似的方案。不过在熔断里面是负载均衡器后续不再发请求,而在限流这里还是会发送请求,只是会降低转发请求到该节点的概率。调整节点的权重就能达成这种效果。
怎么确定限流阈值
观测业务性能数据
我们公司有完善的监控,所以我可以通过观测到的性能数据来确定阈值。比如说观察线上的数据,如果在业务高峰期整个集群的 QPS 都没超过 1000,那么就可以考虑将阈值设定在 1200,多出来的 200 就是余量。
不过这种方式有一个要求,就是服务必须先上线,有了线上的观测数据才能确定阈值。并且,整个阈值很有可能是偏低的。因为业务巅峰并不意味着是集群性能的瓶颈。如果集群本身可以承受每秒 3000 个请求,但是因为业务量不够,每秒只有 1000 个请求,那么我这里预估出来的阈值是显著低于集群真实瓶颈 QPS 的。
压测
不过我个人觉得,最好的方式应该是在线上执行全链路压测,测试出瓶颈。即便不能做全链路压测,也可以考虑模拟线上环境进行压测,再差也应该在测试环境做一个压力测试。
借鉴链路上的其他服务
不过如果真的做不了,或者来不及,或者没资源,那么还可以考虑参考类似服务的阈值。比如说如果 A、B 服务是紧密相关的,也就是通常调用了 A 服务就会调用 B 服务,那么可以用 A 已经确定的阈值作为 B 的阈值。又或者 A 服务到 B 服务之间有一个转化关系。比如说创建订单到支付,会有一个转化率,假如说是 90%,如果创建订单的接口阈值是 100,那么支付的接口就可以设置为 90。
手动计算
实在没办法了,就只能手动计算了。也就是沿着整条调用链路统计出现了多少次数据库查询、多少次微服务调用、多少次第三方中间件访问,如 Redis,Kafka 等。举一个最简单的例子,假如说一个非常简单的服务,整个链路只有一次数据库查询,这是一个会回表的数据库查询,根据公司的平均数据这一次查询会耗时 10ms,那么再增加 10 ms 作为 CPU 计算耗时。也就是说这一个接口预期的响应时间是 20ms。如果一个实例是 4 核,那么就可以简单用 1000ms÷20ms×4=200 得到阈值。
四种静态限流算法
令牌桶
系统会以一个恒定的速率产生令牌,这些令牌会放到一个桶里面,每个请求只有拿到了令牌才会被执行。每当一个请求过来的时候,就需要尝试从桶里面拿一个令牌。如果拿到了令牌,那么请求就会被处理;如果没有拿到,那么这个请求就被限流了。(当令牌桶已满时,新生成的令牌会被丢弃,不会增加桶中的令牌数量。)
你需要注意,本身令牌桶是可以积攒一定数量的令牌的。比如说桶的容量是 100,也就是这里面最多积攒 100 个令牌。那么当某一时刻突然来了 100 个请求,它们都能拿到令牌。

漏桶
漏桶是指当请求以不均匀的速度到达服务器之后,限流器会以固定的速率转交给业务逻辑。
某种程度上,你可以将漏桶算法看作是令牌桶算法的一种特殊形态。你将令牌桶中桶的容量设想为 0,就是漏桶了。


所以你可以看到,在漏桶里面,令牌产生之后你就需要取走,没取走的话也不会积攒下来。因此漏桶是绝对均匀的,而令牌桶不是绝对均匀的。
固定窗口与滑动窗口
固定窗口是指在一个固定时间段,只允许执行固定数量的请求。比如说在一秒钟之内只能执行 100 个请求。滑动窗口类似于固定窗口,也是指在一个固定时间段内,只允许执行固定数量的请求。区别就在于,滑动窗口是平滑地挪动窗口,而不像固定窗口那样突然地挪动窗口。假设窗口大小是一分钟。此时时间是 t1,那么窗口的起始位置是 t1-1 分钟。过了 2 秒之后,窗口大小依旧是 1 分钟,但是窗口的起始位置也向后挪动了 2 秒,变成了 t1 - 1 分钟 + 2 秒。这也就是滑动的含义。

手写限流算法
参考:
https://blog.csdn.net/z3551906947/article/details/140477024,并且里面阐述了各个算法的优缺点,漏桶是可以用来处理突发流量的。
令牌桶
package limiterimport ("sync""time"
)// TokenBucketLimiter 令牌桶限流器
type TokenBucketLimiter struct {capacity int // 容量currentTokens int // 令牌数量rate int // 发放令牌速率/秒lastTime time.Time // 上次发放令牌时间mutex sync.Mutex // 避免并发问题
}// NewTokenBucketLimiter 创建一个新的令牌桶限流器实例。
func NewTokenBucketLimiter(capacity, rate int) *TokenBucketLimiter {return &TokenBucketLimiter{capacity: capacity,rate: rate,lastTime: time.Now(),currentTokens: 0, // 初始化时桶中没有令牌}
}// TryAcquire 尝试从令牌桶中获取一个令牌。
func (l *TokenBucketLimiter) TryAcquire() bool {l.mutex.Lock()defer l.mutex.Unlock()now := time.Now()interval := now.Sub(l.lastTime) // 计算时间间隔// 如果距离上次发放令牌超过 1/rate 秒,则发放新的令牌if float64(interval) >= float64(time.Second)/float64(l.rate) {// 计算应该发放的令牌数量,但不超过桶的容量newTokens := int(float64(interval)/float64(time.Second)* l.rate) l.currentTokens = minInt(l.capacity, l.currentTokens+newTokens)// 更新上次发放令牌的时间l.lastTime = now}// 如果桶中没有令牌,则请求失败if l.currentTokens == 0 {return false}// 桶中有令牌,消费一个令牌l.currentTokens--return true
}// minInt 返回两个整数中的较小值。
func minInt(a, b int) int {if a < b {return a}return b
}func TestName(t *testing.T) {tokenBucket := NewTokenBucketLimiter(5, 10)for i := 0; i < 10; i++ {fmt.Println(tokenBucket.TryAcquire())}time.Sleep(100 * time.Millisecond)fmt.Println(tokenBucket.TryAcquire())
}
漏桶
package limiterimport ("fmt""math""sync""testing""time"
)// LeakyBucketLimiter 漏桶限流器
type LeakyBucketLimiter struct {capacity int // 桶容量currentLevel int // 当前水位rate int // 水流速度/秒lastTime time.Time // 上次放水时间mutex sync.Mutex // 避免并发问题
}// NewLeakyBucketLimiter 初始化漏桶限流器
func NewLeakyBucketLimiter(capacity, rate int) *LeakyBucketLimiter {return &LeakyBucketLimiter{capacity: capacity,currentLevel: 0, // 初始化时水位为0rate: rate,lastTime: time.Now(),}
}// TryAcquire 尝试获取处理请求的权限
func (l *LeakyBucketLimiter) TryAcquire() bool {l.mutex.Lock() // 直接获取写锁defer l.mutex.Unlock()// 如果上次放水时间距今不到 1/rate 秒,不需要放水now := time.Now()interval := now.Sub(l.lastTime)// 计算放水后的水位if float64(interval) >= float64(time.Second)/float64(l.rate) {l.currentLevel = int(math.Max(0, float64(l.currentLevel)-float64(interval)/float64(time.Second)*float64(l.rate)))l.lastTime = now}// 尝试增加水位if l.currentLevel < l.capacity {l.currentLevel++return true}return false
}func TestName(t *testing.T) {tokenBucket := NewLeakyBucketLimiter(5, 10)for i := 0; i < 10; i++ {fmt.Println(tokenBucket.TryAcquire())}time.Sleep(100 * time.Millisecond)fmt.Println(tokenBucket.TryAcquire())
}
固定窗口
package mainimport ("sync""time"
)// FixedWindowRateLimiter 定义固定窗口限流器
type FixedWindowRateLimiter struct {mu sync.MutexmaxRequests intrequestCount intwindow time.Time // 窗口的起始点,每个窗口长度1s
}// NewFixedWindowRateLimiter 创建一个新的固定窗口限流器实例
func NewFixedWindowRateLimiter(maxRequests int) *FixedWindowRateLimiter {return &FixedWindowRateLimiter{maxRequests: maxRequests,window: time.Now().Truncate(time.Second),}
}// TryAcquire 尝试获取请求许可
func (f *FixedWindowRateLimiter) TryAcquire() bool {f.mu.Lock()defer f.mu.Unlock()// 检查是否需要重置窗口if time.Now().After(f.window.Add(time.Second)) {f.requestCount = 0f.window = time.Now().Truncate(time.Second)}// 检查是否达到最大请求次数if f.requestCount >= f.maxRequests {return false}// 请求成功,递增计数器f.requestCount++return true
}func main() {limiter := NewFixedWindowRateLimiter(5)for i := 0; i < 10; i++ {if limiter.TryAcquire() {fmt.Println("请求通过")} else {fmt.Println("请求被拒绝")}time.Sleep(100 * time.Millisecond)}
}
滑动窗口
package mainimport ("sync""time"
)// SlidingWindowRateLimiter 定义滑动窗口限流器
type SlidingWindowRateLimiter struct {mu sync.MutexmaxRequests intwindowSize time.Duration // 窗口长度windows []intwindowIndex intcurrentTime time.Time // 上个滑窗的起始点
}// NewSlidingWindowRateLimiter 创建一个新的滑动窗口限流器实例
func NewSlidingWindowRateLimiter(maxRequests int, windowSize time.Duration) *SlidingWindowRateLimiter {numWindows := int(windowSize.Seconds())return &SlidingWindowRateLimiter{maxRequests: maxRequests,windowSize: windowSize,windows: make([]int, numWindows),currentTime: time.Now().Truncate(time.Second),windowIndex: 0,}
}// TryAcquire 尝试获取请求许可
func (s *SlidingWindowRateLimiter) TryAcquire() bool {s.mu.Lock()defer s.mu.Unlock()// 更新当前时间currentTime := time.Now().Truncate(time.Second)// 检查是否需要更新窗口if currentTime.After(s.currentTime.Add(s.windowSize)) {s.currentTime = currentTimes.windowIndex = 0} else if currentTime.After(s.currentTime.Add(time.Second)) {s.windowIndex = (s.windowIndex + 1) % len(s.windows)}// 清除过期窗口for i := range s.windows {if currentTime.Before(s.currentTime.Add(time.Duration(i+1)*time.Second)) {break}s.windows[i] = 0}// 检查是否达到最大请求次数totalRequests := 0for _, count := range s.windows {totalRequests += count}if totalRequests >= s.maxRequests {return false}// 请求成功,递增计数器s.windows[s.windowIndex]++return true
}func main() {limiter := NewSlidingWindowRateLimiter(5, 10*time.Second)for i := 0; i < 10; i++ {if limiter.TryAcquire() {fmt.Println("请求通过")} else {fmt.Println("请求被拒绝")}time.Sleep(100 * time.Millisecond)}
}
分布式限流的具体实现
从单机或者集群的角度看,可以分为单机限流或者集群限流。集群限流一般需要借助 Redis 之类的中间件来记录流量和阈值。换句话说,就是你需要用 Redis 等工具来实现前面提到的限流算法。当然如果是利用网关来实现集群限流,那么可以摆脱 Redis。
相关文章:
封装一个细粒度的限流器
文章目录 原因限流对象限流后的做法怎么确定限流阈值观测业务性能数据压测借鉴链路上的其他服务手动计算 四种静态限流算法令牌桶漏桶固定窗口与滑动窗口 手写限流算法令牌桶漏桶固定窗口滑动窗口 分布式限流的具体实现 原因 尽管云原生网关里有统一入口的限流(根据…...
【Spring Boot - 注解】@ResponseBody 注解:处理 JSON 响应
文章目录 一、ResponseBody 注解概述1. 注解的功能2. 主要功能 二、ResponseBody 的工作原理1. 接口定义2. 消息转换器3. 自动配置与默认行为 三、ResponseBody 的应用场景1. RESTful API 的实现2. 返回复杂数据结构3. 错误处理和异常处理 四、ResponseBody 的配置和自定义1. 自…...
无人机航拍与ArcGIS融合实战:从地表观测到空间数据可视化的全方位指南!无人机图像拼接数据处理与分析、可视化与制图
目录 第一章 无人机航拍基本流程、航线规划与飞行实践 第二章 无人机图像拼接软件的学习与操作实践 第三章 无人机图像拼接典型案例详解 第四章 无人机图像拼接数据在GIS中的处理与分析 第五章 无人机图像拼接数据在GIS中的可视化与制图 第六章 综合案例:无人机航拍植被动…...
日期转时间濯
tfunction(date_str) local code ,time World:getTimeFromDateString(date_str) return time/(60*60*24) end print(t(2024-08-16)-t(2024-08-3))...
【计算机网络】TCP实战
其实有了UDP的基础,TCP不管怎么说学习起来都还是比较舒服的,至少是比直接就学习TCP的感觉好。 这篇文章最多就是介绍一下起手式,如果想带业务的话和UDP那篇是完全一样的,就不进行演示了。 总的来说还是很简单的。 目录 Echo服务端…...
使用Python制作贪吃蛇小游戏
引言 贪吃蛇游戏是一款经典的电子游戏,玩家通过控制一条不断增长的蛇在格子内移动,并吃掉随机出现的食物来获得分数。随着分数的增加,蛇的身体也会越来越长,游戏的难度也随之提升。在本文中,我们将详细介绍如何使用Py…...
线程的退出
方式1 pthread_exit Void pthread_exit (void *retval) 功能: 结束调用的线程 参数: retval //退出状态值 //需要传的是,退出状态值的地址 注意: 1.pthread_exit 本身表示结束线程 如果用在main函数中 表示结束主线程…...
【AI 绘画】Q版人物定制生成
AI 绘画-PulID手办定制 1. 效果展示 本次测试主要结果展示如下: 牛仔风 古风 2. 基本原理 PuLID是一种类似于 ip-adapter 的恢复面部特征的方法。它同时使用 insightface 嵌入和 CLIP 嵌入,类似于 ip-adapter faceid plus 模型所做的。但是,在将图像传递给 CLIP 之前,还…...
Python爬虫——爬取某网站的视频
爬取视频 本次爬取,还是运用的是requests方法 首先进入bilibili官网中,选取你想要爬取的视频,进入视频播放页面,按F12,将网络中的名称栏向上拉找到第一个并点击,可以在标头中,找到后续我们想要…...
Android逆向题解攻防世界-easy-apk
Jeb反编译apk 题目比较简单,就是一个改了码表的base64编码。 protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);this.setContentView(0x7F04001B); // layout:activity_main((Button)this.findViewById(0x7F0B0076)).set…...
Linux系统使用Typecho搭建个人网站并一键发布公网远程管理本地站点
文章目录 前言1. 安装环境2. 下载Typecho3. 创建站点4. 访问Typecho5. 安装cpolar6. 远程访问Typecho7. 固定远程访问地址8. 配置typecho 💡 推荐 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大…...
机器学习速成第三集——无监督学习之聚类(理论部分)!
目录 聚类的定义和原理 常见的聚类算法 应用场景 总结 无监督学习中聚类算法的最新进展是什么? K-Means聚类算法在处理大规模数据集时的性能优化方法有哪些? 并行计算模型: 多核处理器优化: 分层抽样: 特征缩…...
【机器学习】CNN的基本架构模块
🌈个人主页: 鑫宝Code 🔥热门专栏: 闲话杂谈| 炫酷HTML | JavaScript基础 💫个人格言: "如无必要,勿增实体" 文章目录 CNN的基本架构模块1. 引言2. 卷积层2.1 基本原理2.2 卷积层的特性2.3 卷积层的超…...
第八节AWK报告生成器(2)
3,1,2 printf 语法: printf("format\n", [arguments])格式说明 format是一个用来描述输出格式的字符串,format格式的指示符都以%开头,后跟一个字符,如下: format含义%c显示字符的asicll%d,%i十进制整数%e,%E科学计数法显示数值…...
Linux 进程间通信之管道
个人主页:仍有未知等待探索-CSDN博客 专题分栏: Linux 目录 一、通信 1、进程为什么要通信? 1.数据的类型 2.父进程和子进程算通信吗? 2、进程如何通信? 3、进程通信的常见方式? 二、管道 1、概念…...
IDEA 无法启动,点击之后没有任何提示或者界面
当你尝试通过双击或以管理员身份启动程序时,均未能成功,且未收到任何提示信息或界面反馈,这确实令人困扰。为了诊断问题并获取有用的错误信息,你可以按照以下步骤操作: 1. 启用并查看错误信息 首先,你需要…...
ctf 堆栈结构
CTF(Capture The Flag)竞赛中,理解堆栈结构对于解决涉及二进制分析、逆向工程和利用开发的挑战至关重要。堆栈是在程序执行过程中用于临时存储数据和管理函数调用的关键数据结构。以下是堆栈结构的基本概念及其在CTF竞赛中的应用:…...
sqlserver的openquery配置
1.命令Demo ---openquery--开启Ad Hoc Distributed Queries组件,在sql查询编辑器中执行如下语句exec sp_configure show advanced options,1reconfigureexec sp_configure Ad Hoc Distributed Queries,1reconfigure--关闭Ad Hoc Distributed Queries组件࿰…...
Spring boot logback日志框架加载初始化源码
##LoggingApplicationListener监听 Overridepublic void onApplicationEvent(ApplicationEvent event) {if (event instanceof ApplicationStartingEvent) {onApplicationStartingEvent((ApplicationStartingEvent) event);}else if (event instanceof ApplicationEnvironment…...
qt-11基本对话框(消息框)
基本对话框--消息框 msgboxdlg.hmsgboxdlg.cppmain.cpp运行图QustionMsgInFormationMsgWarningMsgCriticalMsgAboutMsgAboutAtMsg自定义 msgboxdlg.h #ifndef MSGBOXDLG_H #define MSGBOXDLG_H#include <QDialog> #include <QLabel> #include <QPushButton>…...
实战笔记:基于STM32F4的LWIP+FreeRTOS系统移植与网络任务创建
1. 为什么需要LWIPFreeRTOS组合 在嵌入式开发中,网络功能越来越成为标配需求。STM32F4系列凭借其出色的性能和丰富的外设资源,成为许多物联网设备的首选。但要让这个硬件平台真正发挥网络能力,我们需要解决两个核心问题:实时任务调…...
哔哩哔哩API神器bilibili-api:Python开发者的终极爬虫工具指南
哔哩哔哩API神器bilibili-api:Python开发者的终极爬虫工具指南 【免费下载链接】bilibili-api 哔哩哔哩常用API调用。支持视频、番剧、用户、频道、音频等功能。原仓库地址:https://github.com/MoyuScript/bilibili-api 项目地址: https://gitcode.com…...
【紧急通知】Python 3.14 JIT默认profile已触发AWS Lambda冷启动恶化阈值!立即执行这4项低成本开关校准
第一章:Python 3.14 JIT编译器冷启动恶化现象的紧急定性Python 3.14 引入的实验性 JIT 编译器(基于 pyjion 改进的 cpython-jit 后端)在首次执行高密度计算函数时,观测到显著的冷启动延迟激增——部分基准测试中延迟较 Python 3.1…...
电动循迹小车坡道行驶系统设计与实现
1. 坡道行驶电动小车设计解析1.1 系统概述本设计实现了一款具备坡道行驶能力的电动循迹小车系统,采用差速转向方案完成固定路径的循迹功能。系统核心功能包括:四路光电传感器黑线检测差速转向控制算法可编程坡道动力补偿自动停车功能2. 硬件设计2.1 主控…...
DownKyi:解决B站视频下载痛点的创新方案——从低效操作到高效管理的完整实践
DownKyi:解决B站视频下载痛点的创新方案——从低效操作到高效管理的完整实践 【免费下载链接】downkyi 哔哩下载姬downkyi,哔哩哔哩网站视频下载工具,支持批量下载,支持8K、HDR、杜比视界,提供工具箱(音视频…...
化妆镜前扮精致,脊柱 “被扯得变形错位”!
低头化妆、整理发型、涂抹护肤品、搭配饰品,颈腰椎损伤风险显著。低头时颈椎前伸角度过大,肌肉持续紧张痉挛;久坐化妆时腰部缺乏支撑,腰椎同步受累;反复低头抬头动作,导致颈肩腰背肌肉协同疲劳。长期如此&a…...
深度解析Wiki.js操作日志系统:构建企业级安全监控的完整方案
深度解析Wiki.js操作日志系统:构建企业级安全监控的完整方案 【免费下载链接】wiki- Wiki.js | A modern and powerful wiki app built on Node.js 项目地址: https://gitcode.com/GitHub_Trending/wiki78/wiki- 当团队协作编辑Wiki内容时,你是否…...
零基础玩转CosyVoice:3步完成声音克隆,制作专属语音祝福
零基础玩转CosyVoice:3步完成声音克隆,制作专属语音祝福 1. 引言:让声音成为你的专属礼物 你有没有想过,用自己或亲友的声音,生成一段独一无二的语音祝福?比如,用妈妈的声音说“生日快乐”&am…...
Git-RSCLIP模型在计算机网络教学中的应用
Git-RSCLIP模型在计算机网络教学中的应用 1. 引言 计算机网络课程的教学一直面临着抽象概念多、协议交互复杂、拓扑结构难以直观展示的挑战。传统的教学方式往往依赖于静态的图表和文字描述,学生很难真正理解数据包在网络中的流动过程、协议之间的交互关系&#x…...
如何将MacBook刘海变成你的私人文件中转站:NotchDrop完整使用指南
如何将MacBook刘海变成你的私人文件中转站:NotchDrop完整使用指南 【免费下载链接】NotchDrop Use your MacBooks notch like Dynamic Island for temporary storing files and AirDrop 项目地址: https://gitcode.com/gh_mirrors/no/NotchDrop 你是否曾觉得…...
