使用Java内存级方式 和 Redis Lua 脚本方式实现滑动窗口限流

日常业务系统中,限流一般分为两种限流场景:

  • 1、基于服务自身保护的应用自身限流。比如每一个启动的 springboot 实例服务都可以受理最大每秒150个请求的量,服务需要保护自身不被击垮对启动的每一个服务实例都进行限流处理。
  • 2、基于业务系统入口的总限流。比如某业务对外提供了一个api接口,系统经过实测日常可以承载最大10000每秒的请求频率,但是该接口是提供给很多供应商同时使用的,因业务规则实际需要,要求对某个具体渠道的调用最大限流是50,这种就是在总入口处进行限流)。

其中第1种场景,使用 Java 内存级的限流即可实现。
对于第2种场景,需要使用例如 Redis 这样高性能的共享存储的方式来实现。

基于 Java 代码的限流

本文使用 Java JUC 包中的 ConcurrentSkipListMapConcurrentLinkedQueue 集合来实现滑动窗口限流。

示例一,使用 ConcurrentSkipListMap

import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.TimeUnit;public class SlidingWindowRateLimiter {private final long windowSizeMs;private final int maxRequests;private final ConcurrentSkipListMap<Long, Integer> requestTimestamps;public SlidingWindowRateLimiter(long windowSizeMs, int maxRequests) {this.windowSizeMs = windowSizeMs;this.maxRequests = maxRequests;this.requestTimestamps = new ConcurrentSkipListMap<>();}public boolean allowRequest() {long currentTime = System.currentTimeMillis();long startTime = currentTime - windowSizeMs;// 移除超过时间窗口的请求requestTimestamps.headMap(startTime, false).clear();// 统计当前时间窗口内的请求数量int currentRequests = requestTimestamps.size();if (currentRequests < maxRequests) {requestTimestamps.put(currentTime, 1);return true;} else {return false;}}public static void main(String[] args) throws InterruptedException {SlidingWindowRateLimiter limiter = new SlidingWindowRateLimiter(1000, 5);for (int i = 0; i < 10; i++) {if (limiter.allowRequest()) {System.out.println("Request " + i + " allowed at " + System.currentTimeMillis());} else {System.out.println("Request " + i + " denied at " + System.currentTimeMillis());}TimeUnit.MILLISECONDS.sleep(100);}}
}

示例二,使用 ConcurrentLinkedQueue

import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;public class SlidingWindowRateLimiter2 {private final long windowSizeMs;private final int maxRequests;private final ConcurrentLinkedQueue<Long> requestTimestamps;public SlidingWindowRateLimiter2(long windowSizeMs, int maxRequests) {this.windowSizeMs = windowSizeMs;this.maxRequests = maxRequests;this.requestTimestamps = new ConcurrentLinkedQueue<>();}public boolean allowRequest() {long currentTime = System.currentTimeMillis();long startTime = currentTime - windowSizeMs;// 移除超过时间窗口的请求while (!requestTimestamps.isEmpty() && requestTimestamps.peek() < startTime) {requestTimestamps.poll();}// 统计当前时间窗口内的请求数量int currentRequests = requestTimestamps.size();if (currentRequests < maxRequests) {requestTimestamps.add(currentTime);return true;} else {return false;}}public static void main(String[] args) throws InterruptedException {SlidingWindowRateLimiter limiter = new SlidingWindowRateLimiter(1000, 5);for (int i = 0; i < 10; i++) {if (limiter.allowRequest()) {System.out.println("Request " + i + " allowed at " + System.currentTimeMillis());} else {System.out.println("Request " + i + " denied at " + System.currentTimeMillis());}TimeUnit.MILLISECONDS.sleep(100);}}
}

对比总结:

ConcurrentSkipListMap 的主要特性就是进行查找、插入和删除操作时更高效,内部是基于跳表结构实现的。可以保证Key的顺序。
ConcurrentLinkedQueue 的顾名思义就是队列,查找效率相对较低,但是内存占用比 ConcurrentSkipListMap 少一点。顺序严格安装入队的顺序。

  • 如果时间窗口内的请求数量较大,并且你需要高效的查找和移除操作,推荐使用 ConcurrentSkipListMap。它提供了有序性和高效的 O(log n) 操作,适合大规模数据的处理。
  • 如果时间窗口内的请求数量较小,并且你更关心内存开销和插入/删除的效率,推荐使用 ConcurrentLinkedQueue。它提供了 O(1) 的插入和删除操作,适合小规模数据的处理。

绝大部分的应用,其实不比太纠结,两者随便选用。

基于 Redis 的限流脚本

在实际项目应用中,我们的服务实例是多个的,在内存中使用有序集合来实现限流就不可行了,下面是 Redis 使用 lua 脚本进行限流的脚本,可以参考使用:

--KEYS[1]: 限流 key
--ARGV[1]: 限流窗口,毫秒
--ARGV[2]: 当前时间戳(作为score)
--ARGV[3]: 阈值
--ARGV[4]: score 对应的唯一value
-- 1\. 移除开始时间窗口之前的数据
redis.call('zremrangeByScore', KEYS[1], 0, ARGV[2]-ARGV[1])
-- 2\. 统计当前元素数量
local res = redis.call('zcard', KEYS[1])
-- 3\. 是否超过阈值
if (res == nil) or (res < tonumber(ARGV[3])) thenredis.call('zadd', KEYS[1], ARGV[2], ARGV[4])redis.call('expire', KEYS[1], ARGV[1]/1000)return 0
elsereturn 1
end

(END)

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CSharp OpenAI

微软有个开源框架&#xff0c;可以使用C#调用OpenAI接口。地址如下&#xff1a; GitHub - microsoft/semantic-kernel: Integrate cutting-edge LLM technology quickly and easily into your apps 今天研究了下&#xff0c;看如果使用国内代理来使用OpenAI的API。 国内代理…

华为云计算知识总结——及案例分享

目录 一、华为云计算基础知识二、华为云计算相关案例实战案例一&#xff1a;搭建弹性云服务器&#xff08;ECS&#xff09;并部署Web应用案例二&#xff1a;构建基于OBS的图片存储和分发系统案例三&#xff1a;基于RDS的高可用数据库应用案例四&#xff1a;使用华为云DDoS防护保…

11.1组会汇报-基于区块链的安全多方计算研究现状与展望

基础知识 *1.背书&#xff0c;这个词源来自银行票据业务&#xff0c;是指票据转让时&#xff0c;原持有人在票据背面加盖自己的印鉴&#xff0c;证明该票据真实有效、如果有问题就可以找原持有人。 区块链中的背书就好理解了。可以简单的理解为验证交易并声明此交易合法&…

【Linux】进程间通信(命名管道、共享内存、消息队列、信号量)

作者主页&#xff1a; 作者主页 本篇博客专栏&#xff1a;Linux 创作时间 &#xff1a;2024年11月2日 命名管道&#xff1a; 如果我们想在不相关的进程之间交换数据&#xff0c;可以使用FIFO文件来做这项工作&#xff0c;它经常被称为命名管道。命名管道是一种特殊类型的文…

划界与分类的艺术:支持向量机(SVM)的深度解析

划界与分类的艺术&#xff1a;支持向量机&#xff08;SVM&#xff09;的深度解析 1. 引言 支持向量机&#xff08;Support Vector Machine, SVM&#xff09;是机器学习中的经典算法&#xff0c;以其强大的分类和回归能力在众多领域得到了广泛应用。SVM通过找到最优超平面来分…

Java设计模式(代理模式整理中ing)

一、代理模式 1、代理模式定义&#xff1a; 代理模式&#xff1a;由于某些原因要给某对象提供一个代理以控制对该对象的访问&#xff0c;这时访问对象不适合或者不能够直接引用目标对象&#xff0c;代理对象作为访问对象与目标对象之间的中介进行连接调控调用。 2、代理模式的…

【含文档+源码】基于SpringBoot+Vue的新型吃住玩一体化旅游管理系统的设计与实现

开题报告 本文旨在探讨新型吃住玩一体化旅游管理系统的设计与实现。该系统融合了用户注册与登录、旅游景点管理、旅游攻略发帖、特色旅游路线推荐、附近美食推荐以及酒店客房推荐与预定等多项功能&#xff0c;旨在为游客提供全方位、一体化的旅游服务体验。在系统设计中&#…

如何卸载电脑上的软件?彻底删除第三方和系统自带软件方法!(新款)

如何卸载电脑上的软件&#xff1f;在日常使用电脑的过程中&#xff0c;我们经常会安装各种软件以满足不同的需求。然而&#xff0c;随着时间的推移&#xff0c;一些不再使用的软件可能会占用系统资源&#xff0c;影响电脑性能。因此&#xff0c;定期卸载不需要的软件是保持系统…

Linux之du命令

华子目录 du命令常用选项示例注意事项 du命令 du&#xff08;Disk Usage&#xff09;命令是用于在类Unix操作系统&#xff08;如Linux和macOS&#xff09;中显示文件和目录所占用的磁盘空间大小的工具。它可以递归地计算目录和文件的磁盘使用情况&#xff0c;并提供详细的报告…

cocos开发QA

目录 TS相关foreach循环中使用return循环延迟动态获取类属性 Cocos相关属性检查器添加Enum属性使用Enum报错 枚举“XXX”用于其声明前实现不规则点击区域使用cc.RevoluteJoint的enable激活组件无效本地存储以及相关问题JSON.stringify(map)返回{}数据加密客户端复制文本使用客户…

LeetCode :21. 合并两个有序链表(Java)

目录 题目描述: 代码: 第一种: 第二种: 题目描述: 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4]示例 2&#xff1a; …

C 语言标准库 - <limit.h>

简介 <limits.h> 是 C 标准库中的一个头文件&#xff0c;定义了各种数据类型的限制。这些宏提供了有关整数类型&#xff08;char、short、int、long 和 long long 等&#xff09;和其他数据类型的最大值和最小值的信息。 这些限制指定了变量不能存储任何超出这些限制的…

【系统架构设计师】2023年真题论文: 论边云协同的设计与实现(包括解题思路和素材)

更多内容请见: 备考系统架构设计师-专栏介绍和目录 文章目录 真题题目(2023年 试题4)解题思路边云协同概念和架构边云协同的关键技术边云协同的设计与实现案例分析论文素材参考真题题目(2023年 试题4) 边云协同是指将边缘计算和云计算相结合,实现边缘设备与云端资源之间…

Automattic 和 Matt Mullenweg 要求驳回 WP Engine 诉讼案中的关键索赔

Automattic 和 Matt Mullenweg 已提交回应&#xff0c;要求法院驳回 WP Engine 诉讼中的第 1-6 项和第 9-11 项指控。WP Engine 于 2024 年 10 月 18 日向北加州法院提交了一份动议&#xff0c;要求获得初步禁令&#xff0c;寻求恢复对 WordPress.org 的访问&#xff0c;并恢复…

删除的文件怎么找回?删除文件恢复全面指南

我们常常在日常生活或工作中不小心删除了重要文件&#xff0c;这样的情况可能瞬间让人感到无助。不过&#xff0c;数据恢复技术已相当成熟&#xff0c;我们可以通过多种方法来找回误删的文件。下面我们将从简单到复杂逐步讲解找回删除文件的方法&#xff0c;希望可以帮助大家在…

【jvm】如何设置新生代和老年代的比例

目录 1. 说明2. 使用-XX:NewRatio参数3. 使用-Xmn参数4. 配置新生区中的Eden区和Survivor区比例5. 综合配置示例6. 注意事项 1. 说明 1.新生代&#xff08;Young Generation&#xff09;和老年代&#xff08;Old Generation&#xff09;的比例可以通过特定的参数进行设置。2.这…

D57【python 接口自动化学习】- python基础之异常

day57 异常捕获 学习日期&#xff1a;20241103 学习目标&#xff1a;异常 -- 73 异常捕获&#xff1a;出现异常时&#xff0c;如何利用程序进行处理&#xff1f; 学习笔记&#xff1a; try-except代码块 # 捕获异常 num1 num10 try:num/num1except Exception as e:print(上…

【06】A-Maven项目SVN设置忽略文件

做Web项目开发时&#xff0c;运用的是Maven管理工具对项目进行管理&#xff0c;在项目构建的过程中自动生成了很多不需要SVN进行管理的文件&#xff0c;SVN在对源码进行版本管理时&#xff0c;需要将其忽略&#xff0c;本文给出了具体解决方案。 SVN设置忽略Maven项目中自动生成…

logback日志级别动态切换四种方案

生产环境中经常有需要动态修改日志级别。 现在就介绍几种方案 方案一&#xff1a;开启logback的自动扫描更新 配置如下 <?xml version"1.0" encoding"UTF-8"?> <configuration scan"true" scanPeriod"60 seconds" debug…

Linux——Ubuntu的基础操作

压缩与解压缩 gzip压缩工具 创建文件 a.c和b.c touch a.c touch b.c 压缩文件a.c和b.c gzip a.c gzip b.c 解压缩a.c.gz和b.c.gz gzip -d a.c.gz 对文件夹进行压缩 gzip -r 对文件夹进行解压缩 gzip -rd 注意&#xff1a;这只是对文件夹里所有文件进行压缩&#xff0c…