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

news/2024/11/1 9:23:43/

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

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

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

基于 Java 代码的限流

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

示例一,使用 ConcurrentSkipListMap

java">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

java">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.ppmy.cn/news/1543540.html

相关文章

Fsm3

采用读热码编写方式&#xff1a; module top_module(input clk,input in,input areset,output out); ////reg [3:0]A 4d0001;// reg [3:0]B 4d0010;//reg [3:0]C 4d0100;// reg [3:0]D 4d1000; //1、首先用读热码定义四个状态变量parameter A 4d0001 ,B 4d0010, C 4d01…

2、课程大纲、学习方法

1、课程大纲 2、学习方法 1、遗忘曲线 复习就是在不停的梳理自己的知识 理解基础上在你忘记之前不停重复 抓住主线做一个web版本的连接数据库的增删改查 2、学习成长曲线图 3、学习金字塔 只听和阅读肯定是不够的&#xff0c;最基本的是需要做笔记&#xff0c;复习&#xff0…

在pycharm中使用sqllite

在pycharm中使用sqllite sqllite 简介 SQLite 是一个开源的、轻量级的、关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;它设计用于嵌入到应用程序中&#xff0c;并且可以在无需外部服务器进程的情况下运行。SQLite 提供了完整的 SQL 语言支持&#xff0c;允…

Flutter 13 网络层框架架构设计,支持dio等框架。

在移动APP开发过程中&#xff0c;进行数据交互时&#xff0c;大多数情况下必须通过网络请求来实现。客户端与服务端常用的数据交互是通过HTTP请求完成。面对繁琐业务网络层&#xff0c;我们该如何通过网络层架构设计来有效解决这些问题&#xff0c;这便是网络层框架架构设计的初…

Java项目实战II基于Java+Spring Boot+MySQL的编程训练系统(源码+数据库+文档)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 在当今数字…

Tomcat异常日志中文乱码怎么解决

Tomcat异常日志中文乱码怎么解决 tomcat日志中文乱码问题输出其他日志方法解决方法网页报错中文乱码问题我之前试过的方法我的怀疑 能帮我瞅瞅网页报错中文乱码具体该怎么解决吗&#xff1f;可以直接跳转到目录中 网页报错中文乱码问题部分&#x1f913; tomcat日志中文乱码问…

Python 从入门到实战43(Pandas数据结构)

我们的目标是&#xff1a;通过这一套资料学习下来&#xff0c;可以熟练掌握python基础&#xff0c;然后结合经典实例、实践相结合&#xff0c;使我们完全掌握python&#xff0c;并做到独立完成项目开发的能力。 上篇文章我们学习了NumPy数组操作的相关基础知识。今天学习一下pa…

力扣题目解析--Z字形变换

题目 将一个给定字符串 s 根据给定的行数 numRows &#xff0c;以从上往下、从左到右进行 Z 字形排列。 比如输入字符串为 "PAYPALISHIRING" 行数为 3 时&#xff0c;排列如下&#xff1a; P A H N A P L S I I G Y I R 之后&#xff0c;你的输出需要从…