用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.rhkb.cn/news/286492.html

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

相关文章

吴恩达深度学习笔记:浅层神经网络(Shallow neural networks)3.1-3.5

目录 第一门课&#xff1a;神经网络和深度学习 (Neural Networks and Deep Learning)第三周&#xff1a;浅层神经网络(Shallow neural networks)3.1 神经网络概述&#xff08;Neural Network Overview&#xff09;3.2 神经网络的表示&#xff08;Neural Network Representation…

ArkTS编写的HarmonyOS原生聊天UI框架

简介 ChatUI&#xff0c;是一个ArkTS编写的HarmonyOS原生聊天UI框架&#xff0c;提供了开箱即用的聊天对话组件。 下载安装 ohpm install changwei/chatuiOpenHarmony ohpm 环境配置等更多内容&#xff0c;请参考如何安装 OpenHarmony ohpm 包 接口和属性列表 接口列表 接…

Qt实现简易的多线程TCP服务器(附源码)

目录 一.UI界面的设计 二.服务器的启动 三.实现自定义的TcpServer类 1.在widget中声明自定义TcpServer类的成员变量 2.在TcpServer的构造函数中对于我们声明的m_widget进行初始化&#xff0c;m_widget我们用于后续的显示消息等&#xff0c;说白了就是主界面的更新显示等 …

【Java常用API】带目的的爬虫

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收藏 …

学习SpringBoot笔记--知识点(1)

目录 SpringBoot介绍 创建一个最基础的springbooot项目 使用Spring Initializr创建springboot项目 Spring Boot 自动配置机制 SpringBoot常用注解 1.组件注册 2.条件注解 3.属性绑定 SpringBoot自动配置流程​编辑 学习SpringBoot的方法 ​编辑 SpringBoot日志配置…

西井科技与安通控股签署战略合作协议 共创大物流全新生态

2024年3月21日&#xff0c;西井科技与安通控股在“上海硅巷”新象限空间正式签署战略合作框架协议。双方基于此前在集装箱物流的成功实践与资源优势&#xff0c;积极拓展在AI数字化产品、新能源自动驾驶解决方案和多场景应用&#xff0c;以及绿色物流链等领域的深度探索、强强联…

视频汇聚平台EasyCVR启用图形验证码之后调用login接口的操作方法

视频综合管理平台EasyCVR视频监控系统支持多协议接入、兼容多类型设备&#xff0c;平台可以将区域内所有部署的监控设备进行统一接入与集中汇聚管理&#xff0c;实现对监控区域的实时高清视频监控、录像与存储、设备管理、云台控制、语音对讲、级联共享等&#xff0c;在监控中心…

Windows如何搭建 ElasticSearch 集群

单机 & 集群 单台 Elasticsearch 服务器提供服务&#xff0c;往往都有最大的负载能力&#xff0c;超过这个阈值&#xff0c;服务器 性能就会大大降低甚至不可用&#xff0c;所以生产环境中&#xff0c;一般都是运行在指定服务器集群中。 除了负载能力&#xff0c;单点服务器…

【Unity】从0到1的横版2d制作笔记-DAY3

确定碰撞体积 选择rigidbody2d&#xff0c;创建player重力 创建player碰撞体积 创建瓦片地图碰撞体积 使平台变成一个整体 ​​​​​ 设置Body Type为Static&#xff08;避免平台也因为重力影响下落&#xff09; 回到Player&#xff0c;在Rigidbody2D中设置为冻结旋转 Player设…

2016年认证杯SPSSPRO杯数学建模C题(第二阶段)如何有效的抑制校园霸凌事件的发生全过程文档及程序

2016年认证杯SPSSPRO杯数学建模 C题 如何有效的抑制校园霸凌事件的发生 原题再现&#xff1a; 近年来&#xff0c;我国发生的多起校园霸凌事件在媒体的报道下引发了许多国人的关注。霸凌事件对学生身体和精神上的影响是极为严重而长远的&#xff0c;因此对于这些情况我们应该…

SQL映射文件

一、SQL映射的xml文件 1.1 mapper元素 二、select 三、别名与Java映射 四、resultMap 啊

Java毕业设计 基于SSM网上二手书店系统

Java毕业设计 基于SSM网上二手书店系统 SSM jsp 网上二手书店系统 功能介绍 用户&#xff1a;首页 图片轮播 图书查询 图书分类显示 友情链接 登录 注册 图书信息 图片详情 评价信息 加入购物车 资讯信息 资讯详情 个人中心 个人信息 修改密码 意见信息 图书收藏 已经付款 邮…

Golang基础知识(笔记迁移)

golang 变量作用域 局部作用域&#xff1a;代码块、函数内的全局作用域&#xff1a;顶层作用域&#xff0c;代码块外的就是全局&#xff0c;如果变量名大写&#xff0c;则改变量整个程序都可以使用。 类型断言 golang的类型断言在变量后加上.(type)&#xff0c;如果类型断言…

怿星科技Neptune CHT-S测试系统,让智能座舱测试更加高效便捷

随着汽车“智能化”浪潮的推进&#xff0c;汽车的智能化水平正在持续刷新行业认知。在这股智能化潮流中&#xff0c;智能座舱作为客户体验最为直观的部分&#xff0c;其重要性不言而喻。倘若座舱设备出现死机、黑屏、卡顿等现象&#xff0c;都将对客户的使用体验产生非常大的影…

xmes前端问题,给form表单赋值后,再次从表单拿不到该值

xmes前端&#xff0c;给form表单赋值后&#xff0c;再次从表单拿不到该值&#xff0c;但页面可以展示 赋值 this.$[frm-main].$$([namefilm_num]).value filmNum ; 获取表单的值&#xff0c;这里拿不到之前赋的值 const reqData this.$[frm-main].serializeMyForm(); 原因&…

2.7、创建列表(List)

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

Copilot for Microsoft365使用体验

注&#xff1a;本文来自粉丝投稿。 上周进行了留言抽奖&#xff0c;粉丝获得了一周体验资格&#xff0c;并写下了使用体验&#xff0c;特此赠送1个月copilot使用资格。 留言赠送copilot for Microsoft365一周体验卡 每周一Copilot for Microsoft 365留言赠送 上周一通过陈老…

【CPP】智能指针

引言 智能指针是RAII思想的体现&#xff0c;有时候程序抛异常导致指针指向的内存资源未释放&#xff0c;造成内存泄漏&#xff0c;这时就需要用到智能指针&#xff0c;它可以出作用域自动调用析构函数释放内存资源 内存泄漏 什么是内存泄漏 什么是内存泄漏&#xff1a;内存泄…

基于GA优化的CNN-LSTM-Attention的时间序列回归预测matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1卷积神经网络&#xff08;CNN&#xff09;在时间序列中的应用 4.2 长短时记忆网络&#xff08;LSTM&#xff09;处理序列依赖关系 4.3 注意力机制&#xff08;Attention&#xff09; 5…

9.串口通信

串口基本认识 串行接口简称串口&#xff0c;也称串行通信接口或串行通讯接口&#xff08;通常指COM接口&#xff09;&#xff0c;是采用串行通信方 式的扩展接口。串行接口&#xff08;Serial Interface&#xff09;是指数据一位一位地顺序传送。其特点是通信线路简 单&#x…