系统设计 - 设计一个速率限制器
实施速率限制器的位置主要取决于我们的应用程序、技术栈、技术团队等因素。通常有三个位置可供选择:客户端、服务器端或中间件。
客户端是不可靠的地方来执行速率限制,因为恶意行为者可以轻易伪造客户端请求。
比将速率限制器放在服务器端更好的方法是使用速率限制器中间件,它甚至可以对我们的服务器端进行限流。因此,如果您正在使用微服务架构,并且已经使用类似身份验证中间件的功能,则可以在其旁边实现速率限制器中间件。
有许多速率限制算法可供选择:我们将介绍一些算法,包括“令牌桶”、“漏桶”、“滑动窗口计数器”等。
令牌桶算法
Stripe (点击此处阅读)[1] 使用令牌桶算法来限制他们的 API 请求。
根据 Stripe 技术博客:
我们使用令牌桶算法来执行速率限制。这个算法有一个中央的桶主机,你每次请求时都从中取出令牌,并慢慢地向桶中滴入更多令牌。如果桶是空的,就拒绝请求。在我们的情况下,每个 Stripe 用户都有一个桶,每次他们发出请求时,我们就从那个桶中删除一个令牌。我们使用 Redis 实现我们的速率限制器。
桶中有预定义容量的令牌。当请求到达时,它从桶中取出一个令牌并进一步处理。如果没有令牌可供获取,则请求将被丢弃,用户将不得不重试。
示例用例:
•我们将速率限制规则设置为每个用户每分钟 3 个请求。•用户1在 00 时间间隔发出请求,当前桶已满,有 3 个令牌,因此请求将被处理。现在桶中的令牌数量更新为 2。•用户1在第 10 秒发出第二个请求,桶中有 2 个令牌,所以请求将进一步处理。现在桶中的令牌数量更新为 1。•用户1在第 30 秒发出第三个请求,桶中有 1 个令牌,所以请求将进一步处理。现在整整 1 分钟内桶都为空。•用户1在第 55 秒发出第四个请求,桶中没有令牌,因此请求被限制,用户将收到 429 状态码 - 请求太多,并被要求稍后重试。HTTP 429 Too Many Requests 响应状态码表示用户在给定的时间内发送了太多请求。•在那 1 分钟的完成时,令牌计数以固定速率刷新,并且桶再次装满了 3 个令牌,可以再次为该特定用户处理请求。
简单来说:
在令牌桶算法中,我们每次请求都会从桶中处理一个令牌。新令牌以速率 r 放入桶中。桶最多可以容纳 b 个令牌。如果请求到来时桶已满,则该请求将被丢弃。
令牌桶算法需要两个参数:
令牌桶算法代码示例
package mainimport ("fmt""math""time"
)const (MAX_BUCKET_SIZE float64 = 3REFILL_RATE int = 1
)type TokenBucket struct {currentBucketSize float64lastRefillTimestamp int
}func (tb *TokenBucket) allowRequest(tokens float64) bool {tb.refill() //refill of bucket happening at constant REFILL_RATEif tb.currentBucketSize >= tokens {tb.currentBucketSize -= tokensreturn true}return false
}func getCurrentTimeInNanoseconds() int {return time.Now().Nanosecond()
}func (tb *TokenBucket) refill() {nowTime := getCurrentTimeInNanoseconds()tokensToAdd := (nowTime - tb.lastRefillTimestamp) * REFILL_RATE / 1e9tb.currentBucketSize = math.Min(tb.currentBucketSize+float64(tokensToAdd), MAX_BUCKET_SIZE)tb.lastRefillTimestamp = nowTime
}func main() {obj := TokenBucket{currentBucketSize: 3,lastRefillTimestamp: 0,}fmt.Printf("Request processed: %v\n", obj.allowRequest(1)) //truefmt.Printf("Request processed: %v\n", obj.allowRequest(1)) //truefmt.Printf("Request processed: %v\n", obj.allowRequest(1)) //truefmt.Printf("Request processed: %v\n", obj.allowRequest(1)) //false, request dropped
} Leaky Bucket Algorithm
Leaky Bucket是使用队列实现速率限制的一种简单直观的方式。它是一个先进先出的队列(FIFO)。进来的请求被附加到队列中,如果没有足够的空间容纳新的请求,则会被丢弃(泄漏)。
•当一个请求到达时,系统检查队列是否已满。如果没有满,则将请求添加到队列中。•否则,请求将被丢弃。•请求以固定的间隔从队列中提取并处理。
算法的工作原理:
泄漏桶算法接受以下两个参数:
•桶大小:等于队列大小。队列保存要以固定速率处理的请求。•流出速率:定义在固定速率内可以处理多少请求,通常以秒为单位。
该算法的优点是,它平滑处理请求的突发,并以大致平均的速率处理它们。
Sliding Window Algorithm
•该算法跟踪请求的时间戳。时间戳数据通常保存在缓存中,例如 Redis 的有序集合。•当一个新请求到来时,删除所有过期的时间戳。过期的时间戳被定义为早于当前时间窗口的开始时间。•将新请求的时间戳添加到日志中。•如果日志大小与允许的请求数相同或更低,则接受请求。否则,拒绝该请求。
在下面的示例中,速率限制器允许我们每分钟2个请求。窗口内超出此限制的请求将被丢弃。
注意: 为了便于理解,我们使用了hh:mm:ss格式,但在redis中,我们将推送UNIX时间戳。
•当一个新请求在1:00:01到达时,日志为空。因此,请求被允许。•一个新请求在1:00:30到达,时间戳1:00:30被插入日志中。插入后,日志大小为2,不大于允许的请求数。因此,请求被允许。•一个新请求在1:00:50到达,时间戳被插入到日志中。插入后,日志大小为3,大于允许的大小2。因此,该请求被拒绝,即使时间戳仍然存在于日志中。•一个新请求在1:01:40到达。在[1:00:40,1:01:40)范围内的请求在最新时间范围内,但在1:00:40之前发送的请求已过时。两个过时的时间戳1:00:01和1:00:30从日志中删除。删除操作后,日志大小变为2;因此,该请求被接受。
速率限制器的详细设计:
我们将使用以下组件:
•配置和数据存储来存储速率限制器规则。•工作进程经常从磁盘中获取规则并将其存储在缓存中。•速率限制器中间件从缓存中获取规则。它还从Redis中获取时间戳、计数器等数据。当请求到来时,它进行计算并决定是处理该请求还是对其进行速率限制。•如果需要对请求进行速率限制,这里有两个选项•选项1:拒绝请求并向客户端发送状态代码429: too many requests。•选项2:将请求推送到消息队列以稍后处理该请求。
现在,我们可能需要将以上系统扩展到分布式环境中,以支持多个服务器和并发线程。这里可能会出现两个挑战:
竞争条件
如上所述:
从Redis中读取计数器值,检查(计数器+1)是否超过阈值。如果没有,将计数器值在Redis中增加1。
假设Redis中的计数器值为3(如上图所示)。如果两个请求并发读取计数器值,而在任一请求写回值之前,都不检查另一个线程。它们都会将计数器增加1并写回,而不会检查其他线程。两个请求(线程)都认为它们有正确的计数器值4。但是,正确的计数器值应该是5。
锁可以在这里使用,但这可能会影响性能。因此,我们可以使用Redis Lua脚本[2]。
•这可能会导致更好的性能。•此外,脚本中的所有步骤都以原子方式执行。在脚本执行时,没有其他Redis命令可以运行。
同步
在分布式环境中,同步是另一个需要考虑的重要因素。为了支持数百万用户,一个速率限制服务器可能无法处理流量。当使用多个速率限制服务器时,需要同步。
一种可能的解决方案是使用黏性会话,允许客户端将流量发送到相同的速率限制器。这种解决方案既不可扩展也不灵活。更好的方法是使用像Redis这样的集中式数据存储。
在所有事情都就绪之后,我们还可以监视我们的速率限制器以获取性能、规则、算法有效性等指标。例如,如果速率限制规则过于严格,会丢弃许多有效请求。在这种情况下,我们希望稍微放松一下规则。在另一个例子中,我们注意到当有突然增加的流量,如抢购时,我们的速率限制器变得无效。在这种情况下,我们可以更改算法以支持突发流量。令牌桶在这里非常合适。
引用链接
[1] (点击此处阅读): https://stripe.com/blog/rate-limiters[2] Redis Lua脚本: https://www.freecodecamp.org/news/a-quick-guide-to-redis-lua-scripting/
相关文章:
系统设计 - 设计一个速率限制器
实施速率限制器的位置主要取决于我们的应用程序、技术栈、技术团队等因素。通常有三个位置可供选择:客户端、服务器端或中间件。 客户端是不可靠的地方来执行速率限制,因为恶意行为者可以轻易伪造客户端请求。 比将速率限制器放在服务器端更好的方法是使…...
[技术分享]Android平台实时音视频录像模块设计之道
实现背景 录像有什么难的?无非就是数据过来,编码保存mp4而已,这可能是好多开发者在做录像模块的时候的思考输出。是的,确实不难,但是做好,或者和其他模块有非常好的逻辑配合,确实不容易。 好多…...
JDKMissionControl官方用户指南--人工翻译
1. JMC8新增功能 暂时用不到,暂略 2. JDK Mission Control是什么 JMC是一组高级工具,用于管理、监视、分析Java应用程序并排除其故障。JMC能够对代码性能、内存和延迟等领域进行高效而详细的数据分析,而不会引入通常与分析和监控工具相关的…...
MySql-高级(分库分表问题简析) 学习笔记
文章目录 1. 为什么要分库分表?2. 用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?3. 你们具体是如何对数据库如何进行垂直拆分或水平拆分的?4. 分库分表时,数据迁移方案5. 如何设计可以动态扩容缩容…...
【5.20】五、安全测试——安全测试工具
目录 5.4 常见的安全测试工具 1. Web漏洞扫描工具——AppScan 2. 端口扫描工具——Nmap 3. 抓包工具——Fiddler 4. Web渗透测试工具——Metasploit 小提示:Kali Linux 5.4 常见的安全测试工具 安全测试是一个非常复杂的过程,测试所使用到的工具也…...
【13900k】i9 核显升级驱动
这里写自定义目录标题 官方的助手不能用显卡控制中心提示最新的更新搜索显卡 intel uhd graphics 770 手动下载安装自定义音频为啥也要卸载?新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片…...
使用Python将绿色转换为红色、红色转换为蓝色的图像处理
使用Python将绿色转换为红色、红色转换为蓝色的图像处理 在图像处理中,我们经常需要对图像进行颜色转换和修改。本篇博客介绍了如何使用Python的Pillow库来读取一个文件夹中的所有图像,并将其中的绿色转换为红色,红色转换为蓝色。我们还展示…...
Web2与Web3开发的不同之处
Web2是引入交互功能的第二代互联网,也是我们今天所熟悉的。随着Web的不断发展,第三代互联网,也被称为Web3,正处于积极开发中。Web3引入了在区块链上运行的去中心化和无需许可的系统。但是Web2和Web3开发之间有什么区别呢ÿ…...
递增数组的判断【python实现】
有时候需要对某一组数组的数据进行判断是否 递增 的场景,比如我在开发一些体育动作场景下,某些肢体动作是需要持续朝着垂直方向向上变化,那么z轴的值是会累增的。同理,逆向考虑,递减就是它的对立面。 下面是查找总结到…...
在自定义数据上训练 YOLOv8 实例分割
图像分割是一个核心视觉问题,可以为大量用例提供解决方案。从医学成像到分析流量,它具有巨大的潜力。实例分割,即对象检测+分割,甚至更强大,因为它允许我们在单个管道中检测和分割对象。为此,Ultralytics YOLOv8 模型提供了一个简单的管道。在本文中,我们将对自定义数据…...
洛谷密钥被破解:加密安全面临新挑战
密钥管理是加密系统中非常重要的一环,它涉及到密钥的生成、存储、分发、管理和销毁等多个方面。在密码学中,密钥是保护数据隐私和安全性的核心因素之一,因此,确保密钥的安全和保密性显得尤为重要。在2016年举办的 CQOI 数论竞赛中…...
02 Android开机启动之BootLoader及kernel的启动
Android开机启动之BootLoader及kernel的启动 1、booloader的启动流程 第一阶段:硬件初始化,SVC模式,关闭中断,关闭看门狗,初始化栈,进入C代码 第二阶段:cpu/board/中断初始化;初始化内存以及flash,将kernel从flash中拷贝到内存中,执行bootm,启动内核 2、kernel的启…...
代码随想录算法训练营 Day 49 | 121.买卖股票的最佳时机,122.买卖股票的最佳时机 II
121.买卖股票的最佳时机 讲解链接:代码随想录-121.买卖股票的最佳时机 确定 dp 数组以及下标的含义: dp[i][0] 表示第 i 天持有股票所得最多现金dp[i][1] 表示第 i 天不持有股票所得最多现金 确定递推公式: 如果第 i 天持有股票即 dp[i][0]&…...
精炼计算机网络——数据链路层(一)
文章目录 前言3.1 数据链路和帧3.1.1 数据链路和帧3.1.2 三个基本问题 3.2 点对点协议PPP3.2.1 PPP协议的特点3.2.2 PPP协议3.2.3 PPP协议的工作状态 总结 前言 上篇文章,我们一同学完了物理层的全部内容,在本篇文章中,我们初步学习数据链路…...
猿创征文|Spring系列框架之面向切面编程AOP
⭐️前面的话⭐️ 本篇文章将介绍一种特别重要的思想,AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。 …...
IoT架构设计
当前有一个支持5000万用户并发访问的网站,每个用户都有一个IOT设备,用户可以查看设备状态,接受设备通知 1.架构设计 针对不同的业务量模型,可以采用不同的架构设计,如下: 低业务量模型 针对低业务量模型…...
EasyRecovery16电脑硬盘数据恢复软件功能讲解
硬盘是很常见的存储数据的设备,硬盘中很多重要的数据一旦丢失会很麻烦,不过现在有硬盘数据恢复软件可以自行在家恢复数据。今天的文章就带大家来看看硬盘恢复数据的软件EasyRecovery。 EasyRecovery 是一款专业的数据恢复软件,支持恢复不同存…...
信道通信基础 - 传输介质(双绞线、光纤)
文章目录 1 概述2 传输介质2.1 双绞线2.2 光纤 3 扩展3.1 网工软考真题 1 概述 2 传输介质 2.1 双绞线 双绞线:8 根铜导线每 2 根扭在一起(百兆用 4 根,千兆必须用 8 根)分类 2.2 光纤 光纤:利用光在 玻璃或塑料纤…...
黑马Redis原理篇
黑马Redis原理篇 1、数据结构1.1、动态字符串SDS1.2、IntSet1.3、Dict1.4、ZipList1.5、QuickList1.6、SkipList1.7、RedisObject1.8、五种数据结构1. String(小EMBSTR,大RAW (SDS),少量整数INT)2. List(Redis3.2之后使用QuickList实现&#…...
Sql Server增加字段、修改字段、修改类型、修改默认值
1、修改字段名: alter table 表名 rename column A to B 2、修改字段类型: alter table 表名 alter column 字段名 type not null 3、修改字段默认值 alter table 表名 add default (0) for 字段名 with values 如果字段有默认值,则需要…...
基于算法竞赛的c++编程(28)结构体的进阶应用
结构体的嵌套与复杂数据组织 在C中,结构体可以嵌套使用,形成更复杂的数据结构。例如,可以通过嵌套结构体描述多层级数据关系: struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
python如何将word的doc另存为docx
将 DOCX 文件另存为 DOCX 格式(Python 实现) 在 Python 中,你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是,.doc 是旧的 Word 格式,而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...
BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践
6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...
python报错No module named ‘tensorflow.keras‘
是由于不同版本的tensorflow下的keras所在的路径不同,结合所安装的tensorflow的目录结构修改from语句即可。 原语句: from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后: from tensorflow.python.keras.lay…...
Java编程之桥接模式
定义 桥接模式(Bridge Pattern)属于结构型设计模式,它的核心意图是将抽象部分与实现部分分离,使它们可以独立地变化。这种模式通过组合关系来替代继承关系,从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...
【无标题】路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论
路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论 一、传统路径模型的根本缺陷 在经典正方形路径问题中(图1): mermaid graph LR A((A)) --- B((B)) B --- C((C)) C --- D((D)) D --- A A -.- C[无直接路径] B -…...
