用redis lua脚本实现时间窗分布式限流

需求背景:

限制某sql在30秒内最多只能执行3次

需求分析

微服务分布式部署,既然是分布式限流,首先自然就想到了结合redis的zset数据结构来实现。
分析对zset的操作,有几个步骤,首先,判断zset中符合rangeScore的元素个数是否已经达到阈值,如果未达到阈值,则add元素,并返回true。如果已达到阈值,则直接返回false。

代码实现

首先,我们需要根据需求编写一个lua脚本

redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, tonumber(ARGV[3]))
local res = 0
if(redis.call('ZCARD', KEYS[1]) < tonumber(ARGV[5])) thenredis.call('ZADD', KEYS[1], tonumber(ARGV[2]), ARGV[1])res = 1
end
redis.call('EXPIRE', KEYS[1], tonumber(ARGV[4]))
return res

ARGV[1]: zset element
ARGV[2]: zset score(当前时间戳)
ARGV[3]: 30秒前的时间戳
ARGV[4]: zset key 过期时间30秒
ARGV[5]: 限流阈值

private final RedisTemplate<String, Object> redisTemplate;public boolean execLuaScript(String luaStr, List<String> keys, List<Object> args){RedisScript<Boolean> redisScript = RedisScript.of(luaStr, Boolean.class)return redisTemplate.execute(redisScript, keys, args.toArray());
}

测试一下效果

@SpringBootTest
public class ApiApplicationTest {@Testpublic void test2() throws InterruptedException{String luaStr = "redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, tonumber(ARGV[3]))\n" +"local res = 0\n" +"if(redis.call('ZCARD', KEYS[1]) < tonumber(ARGV[5])) then\n" +"    redis.call('ZADD', KEYS[1], tonumber(ARGV[2]), ARGV[1])\n" +"    res = 1\n" +"end\n" +"redis.call('EXPIRE', KEYS[1], tonumber(ARGV[4]))\n" +"return res";for (int i = 0; i < 10; i++) {boolean res = execLuaScript(luaStr, Arrays.asList("aaaa"), Arrays.asList("ele"+i, System.currentTimeMillis(),System.currentTimeMillis()-30*1000, 30, 3));System.out.println(res);Thread.sleep(5000);}}
}

在这里插入图片描述
测试结果符合预期!

扩展阅读

lua脚本每次都需要传一长串脚本内容来回传输,会增加网络流量和延迟,而且每次都需要服务器重新解释和编译,效率较为低下。因此,不建议在实际生产环境中直接执行lua脚本,而应该使用lua脚本的hash值来进行传输。

为了方便使用,我们先把方法封装一下

import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.connection.RedisScriptingCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;import java.util.List;/*** @author 敖癸* @formatter:on* @since 2024/3/25*/
@Component
@RequiredArgsConstructor
public class RedisService {private final RedisTemplate<String, Object> redisTemplate;private static RedisScriptingCommands commands;private static RedisSerializer keySerializer;private static RedisSerializer valSerializer;public String loadScript(String luaStr) {byte[] bytes = RedisSerializer.string().serialize(luaStr);return this.getCommands().scriptLoad(bytes);}public <T> T execLuaHashScript(String hash, Class<T> returnType, List<String> keys, Object[] args) {byte[][] keysAndArgs = toByteArray(this.getKeySerializer(), this.getValSerializer(), keys, args);return this.getCommands().evalSha(hash, ReturnType.fromJavaType(returnType), keys.size(), keysAndArgs);}private static byte[][] toByteArray(RedisSerializer keySerializer, RedisSerializer argsSerializer, List<String> keys, Object[] args) {final int keySize = keys != null ? keys.size() : 0;byte[][] keysAndArgs = new byte[args.length + keySize][];int i = 0;if (keys != null) {for (String key : keys) {keysAndArgs[i++] = keySerializer.serialize(key);}}for (Object arg : args) {if (arg instanceof byte[]) {keysAndArgs[i++] = (byte[]) arg;} else {keysAndArgs[i++] = argsSerializer.serialize(arg);}}return keysAndArgs;}private RedisScriptingCommands getCommands() {if (commands == null) {commands = redisTemplate.getRequiredConnectionFactory().getConnection().scriptingCommands();}return commands;}private RedisSerializer getKeySerializer() {if (keySerializer == null) {keySerializer = redisTemplate.getKeySerializer();}return keySerializer;}private RedisSerializer getValSerializer() {if (valSerializer == null) {valSerializer = redisTemplate.getValueSerializer();}return valSerializer;}
}
  • 测试一下:
@SpringBootTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class ApiApplicationTest implements ApplicationContextAware {private static ApplicationContext context;private static RedisService redisService;public static String luaHash;private final static String LUA_STR = "redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, tonumber(ARGV[3]))\n" +"local res = 0\n" +"if(redis.call('ZCARD', KEYS[1]) < tonumber(ARGV[5])) then\n" +"    redis.call('ZADD', KEYS[1], tonumber(ARGV[2]), ARGV[1])\n" +"    res = 1\n" +"end\n" +"redis.call('EXPIRE', KEYS[1], tonumber(ARGV[4]))\n" +"return res";@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {context = applicationContext;}@BeforeAllpublic static void before(){redisService = context.getBean(RedisService.class);luaHash = redisService.loadScript(LUA_STR);System.out.println("lua脚本hash: "+ luaHash);}@Testpublic void testLuaHash() throws InterruptedException {for (int i = 0; i < 50; i++) {List<String> keys = Collections.singletonList("aaaa");Object[] args = new Object[]{"ele" + i, System.currentTimeMillis(), System.currentTimeMillis() - 30 * 1000, 30, 3};Boolean b = redisService.execLuaHashScript(luaHash, Boolean.class, keys, args);System.out.println(b);Thread.sleep(3000);}}
}

使用的时候在项目启动时候,把脚本load一下,后续直接用hash值就行了
在这里插入图片描述

搞定收工!

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

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

相关文章

使用JMeter进行梯度压测

使用JMeter进行梯度压测 梯度压测配置如下&#xff1a; 使用线程:5&#xff0c;然后循环5000次&#xff0c;共2.5万个样本使用线程:10&#xff0c;然后循环5000次&#xff0c;共5万个样本使用线程:15&#xff0c;然后循环5000次&#xff0c;共7.5万个样本使用线程:20&#xff…

Redis中的事件

事件 概述 Redis服务器是一个事件驱动程序:服务器需要处理以下两类事件: 1.文件事件(file event):Redis服务器通过套接字与客户端(或者其他Redis服务器)进行连接&#xff0c;而文件事件就是服务器对套接字操作的抽象。服务器与客户端(或者其他服务器)的通信会产生相应的文件…

上位机图像处理和嵌入式模块部署(qmacvisual自定义插件代码分析)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 qmacvisual里面的第三方插件主要由两部分组成&#xff0c;一部分是ExtensionLibrary&#xff0c;也就是插件的容器&#xff0c;这个是官方提供的&a…

【机器学习】贝叶斯上篇(详解)

深入理解贝叶斯学习&#xff1a;核心原理及应用全解析 在机器学习的领域内&#xff0c;贝叶斯学习作为一种强大的框架&#xff0c;使我们能够在不确定性条件下进行预测和决策。贝叶斯学习源于托马斯贝叶斯的工作&#xff0c;提供了一种概率论的学习方法&#xff0c;与传统的频…

荟萃分析R Meta-Analyses 3 Effect Sizes

总结 效应量是荟萃分析的基石。为了进行荟萃分析&#xff0c;我们至少需要估计效应大小及其标准误差。 效应大小的标准误差代表研究对效应估计的精确程度。荟萃分析以更高的精度和更高的权重给出效应量&#xff0c;因为它们可以更好地估计真实效应。 我们可以在荟萃分析中使用…

软考高级架构师:MVP 架构概念和例题

作者&#xff1a;明明如月学长&#xff0c; CSDN 博客专家&#xff0c;大厂高级 Java 工程师&#xff0c;《性能优化方法论》作者、《解锁大厂思维&#xff1a;剖析《阿里巴巴Java开发手册》》、《再学经典&#xff1a;《Effective Java》独家解析》专栏作者。 热门文章推荐&am…

Spring - AOP/事务 实现原理

AOP 基本概念 官方文档&#xff1a; Aspect Oriented Programming with Spring Spring AOP supports the following AspectJ pointcut designators (PCD) for use in pointcut expressions: within - limits matching to join points within certain types (simply the exec…

Visual Studio项目编译和运行依赖第三方库的项目

1.创建项目&#xff0c;这里创建的项目是依赖于.sln的项目&#xff0c;非CMake项目 2.添加第三方库依赖的头文件和库文件路劲 3.添加第三方依赖库文件 4.项目配置有2个&#xff0c;一个是Debug&#xff0c;一个是Release&#xff0c;如果你只配置了Debug&#xff0c;编译和运行…

Unity 视频组件 VideoPlayer

组件添加&#xff1a; 在自己定义的组件下&#xff08;例如&#xff1a;Panel&#xff09; 点击 Inspector 面板中的 AddComponent &#xff0c;输入“VideoPlayer”。 资源 这里 视频资源有两种形式&#xff0c;第一种是 VideoClip &#xff0c;需要将视频文件拖拽到该属性字段…

喜报 | 聚合科技荣获江苏省数字经济学会科学技术奖

近日&#xff0c;江苏省数字经济学会公布了2023年度江苏省数字经济学会科学技术奖获奖名单。本次在全省范围内共评选出科学技术进步奖、科学技术创新奖、优秀成果奖获奖项目共计19项。“聚合数据资产服务API平台”凭借其前沿的创新性和优秀的应用前景成功获得科学技术创新奖二等…

说说Loader和Plugin的区别?编写Loader,Plugin的思路?

文章目录 一、区别二、编写loader三、编写plugin参考文献 一、区别 前面两节我们有提到Loader与Plugin对应的概念&#xff0c;先来回顾下 loader 是文件加载器&#xff0c;能够加载资源文件&#xff0c;并对这些文件进行一些处理&#xff0c;诸如编译、压缩等&#xff0c;最终…

基于TensorFlow的花卉识别(算能杯)%%%

Anaconda Prompt 激活 TensorFlow CPU版本 conda activate tensorflow_cpu //配合PyCharm环境 直接使用TensorFlow1.数据分析 此次设计的主题为花卉识别&#xff0c;数据为TensorFlow的官方数据集flower_photos&#xff0c;包括5种花卉&#xff08;雏菊、蒲公英、玫瑰、向日葵…

UI自动化_id 元素定位

## 导包selenium from selenium import webdriver import time1、创建浏览器驱动对象 driver webdriver.Chrome() 2、打开测试网站 driver.get("你公司的平台地址") 3、使浏览器窗口最大化 driver.maximize_window() 4、在用户名输入框中输入admin driver.find_ele…

RSTP环路避免实验(思科)

华为设备参考&#xff1a;RSTP环路避免实验&#xff08;华为&#xff09; 一&#xff0c;技术简介 RSTP (Rapid Spanning Tree Protocol) 是从STP发展而来 • RSTP标准版本为IEEE802.1w • RSTP具备STP的所有功能&#xff0c;可以兼容STP运行 • RSTP和STP有所不同 减少了…

MVC框架里的几种对象

Java语言是一门面向对象的编程语言&#xff0c;所有都用类表达&#xff0c;入口都是一个类&#xff0c;没有独立的main&#xff08;&#xff09;函数&#xff0c;类的实例化就是对象。 简单来讲类包括数据和方法&#xff0c;方法就是操作&#xff0c;是实现业务逻辑的地方&…

Facebook是什么?有什么功能?如何利用Facebook运营?

Facebook&#xff0c;也常被人们称为“脸书”、“脸谱”等&#xff0c;是美国的社交网络服务及社会化媒体网站&#xff0c;拥有超过20亿的月活跃用户&#xff0c;对于众多商家而言&#xff0c;Facebook以其广泛的用户基础和强大的社交影响力&#xff0c;成为了一个理想的社媒营…

视频推拉流EasyDSS点播平台云端录像播放异常的问题排查与解决

视频推拉流EasyDSS视频直播点播平台可提供一站式的视频转码、点播、直播、视频推拉流、播放H.265视频等服务&#xff0c;搭配RTMP高清摄像头使用&#xff0c;可将无人机设备的实时流推送到平台上&#xff0c;实现无人机视频推流直播、巡检等应用。 有用户反馈&#xff0c;项目现…

2.7、创建列表(List)

概述 列表是一种复杂的容器&#xff0c;当列表项达到一定数量&#xff0c;内容超过屏幕大小时&#xff0c;可以自动提供滚动功能。它适合用于呈现同类数据类型或数据类型集&#xff0c;例如图片和文本。在列表中显示数据集合是许多应用程序中的常见要求&#xff08;如通讯录、…

LeetCode 面试经典150题 392.判断子序列

题目&#xff1a; 给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;例如&#xff0c;"ace"是"abcde"…

基于深度学习的OCR,如何解决图像像素差的问题?

基于深度学习的OCR技术在处理图像像素差的问题时确实面临一定的挑战。图像像素差可能导致OCR系统无法准确识别文本&#xff0c;从而影响其精度和可靠性。尽管已经有一些方法如SRN-Deblur、超分SR和GAN系列被尝试用于解决这个问题&#xff0c;但效果并不理想。然而&#xff0c;这…