Jedis使用lua脚本完成令牌桶限流
Jedis使用lua脚本完成令牌桶限流
文章目录
- Jedis使用lua脚本完成令牌桶限流
- 一、lua脚本的简单语法
- 二、令牌桶限流
- 1. 构思
- 2. 实现
- 三、Jemeter压测工具测试
一、lua脚本的简单语法
KEYS[1]
ARGV[1]
这两个参数分别代表了我们传入的key数组的一号元素和arg数组的一号元素
下面来看一个简单的使用
eval “return redis.call(‘get’,KEYS[1])” 1 one
首先eval 是解析Lua脚本的关键字,字符串中的就是写的lua脚本,其中
redis.call()表示调用redis。 1 表示数组的第一位,one表示的就是KEY[1]传入one。
下面演示jedis调用lua脚本实例
public static void main(String[] args) throws IOException {Jedis jedis = new Jedis("127.0.0.1", 6379);ClassPathResource classPathResource = new ClassPathResource("/META-INF/script/redis_limiter.lua");byte[] buffer = new byte[(int)classPathResource.getFile().length()];classPathResource.getInputStream().read(buffer);String script = new String(buffer);System.out.println(script);String lua = jedis.scriptLoad(script);Object evalsha = jedis.evalsha(lua, getKeys(), getArgs());System.out.println(evalsha);}public static List<String> getKeys() {return Arrays.asList("one");}public static List<String> getArgs() {return Arrays.asList("50");}
local key=KEYS[1]
local arg=ARGV[1]local value=tonumber(arg)
local currentValue=tonumber(redis.call('get', key))
if currentValue==nil thencurrentValue=0
end
local new_value=(currentValue+value)redis.call('set',key,new_value)
上面的Lue脚本中,定义了key值和参数值,tonumber()表示转成数字。
这段的大概意思就是获取key的值,判断是否为空,为空的话表示该值从0开始,然后调用redis函数 给key赋新值。
二、令牌桶限流
1. 构思
首先要明确什么是令牌桶限流,其实就是单位时间内向桶中发放令牌,如果有请求进来,请求申请令牌,如果令牌有的话,那么就可以拿到令牌,进行正常的操作,如果获取令牌失败,那么就拒绝访问。(桶中容量要做限制,不然有一段时间没人访问,一直方法令牌,当流量高峰达到,桶中已经屯了很多令牌,没有起到限流的作用)
基于这一目的,我们可以明确二点:
- 恒定速率发放令牌
- 令牌桶有最大容量
2. 实现
借助于Lua脚本我们可以实现原子性,因为redis处理业务的线程是单线程模式(主从复制时会另起线程)
那么我们就可以这样设计
首先在redis中存储一个key-value值表示令牌桶和令牌桶中的令牌数量
然后再存储一个上次访问的时间戳
在lua脚本中我们传入四个参数
- 令牌桶的速率
- 令牌桶的容量
- 请求时 时间戳
- 请求得令牌数量
在使用redis.call()函数调用得到当前令牌桶数和上次刷新得时间戳
我们就可以计算出来这次时间和上次时间之间之差,算出来这两次访问之间产生多少令牌,然后使用 上次访问得令牌桶数+中间访问产生得容量=当前令牌桶中数量。
那么我们就可以判断当前请求得令牌数量是否>当前桶中数量,如果大于return false,如果小于returen ture,并且在返回之前重新刷新redis中存储得令牌桶数和时间戳
下面我们来实战一下
--传入令牌桶得key和时间戳得key
local token_key=KEYS[1]
local time_key=KEYS[2]-- 分别传入 速率,容量,现在得时间,请求得令牌数
local rate=tonumber(ARGV[1])
local capacity=tonumber(ARGV[2])
local now_time=tonumber(ARGV[3])
local requestNum=tonumber(ARGV[4])
--获取上次访问 得令牌树和时间绰
local last_tokens=tonumber(redis.call('get',token_key))
if last_tokens==nil thenlast_tokens=capacity
end
local last_time=tonumber(redis.call('get',time_key))
if last_time==nil thenlast_time=0
end
--1,计算时间差
local time_del=math.max(0,(now_time-last_time))
--2,计算当前令牌桶中应该有多少令牌数
local current_tokens=math.min(capacity,(last_tokens+time_del*rate))
--3,判断是否允许访问
local acuire=0
if (requestNum <= current_tokens) thenacuire=1current_tokens=(current_tokens-requestNum)
end-- 计算一下过期时间 (默认就是填满令牌得时间*2)
local ttl = 60
--4,刷新记录redis.call('setex',token_key,ttl,current_tokens)redis.call('setex',time_key,ttl,now_time)return { acuire }
java代码
限流拦截器
package com.xzq.config;@Component
public class LimiterIntercepter implements HandlerInterceptor, InitializingBean {private Logger logger = LoggerFactory.getLogger(LimiterIntercepter.class);private static String TOKEN_BUCKET = "TOKEN_BUCKET";private static String REFRESH_TIME = "REFRESH_TIME";private String scriptLua;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if (isallow()) {logger.info("允许进入");return true;}else{logger.info("限制进入");response.setStatus(500);return false;}}public boolean isallow() {Jedis jedis = new Jedis("127.0.0.1", 6379);long result = (long)((List) jedis.evalsha(scriptLua, getKeys(), getArgs())).get(0);return result == 1L;}public static List<String> getKeys() {return Arrays.asList(TOKEN_BUCKET, REFRESH_TIME);}public static List<String> getArgs() {String now = String.valueOf(System.currentTimeMillis() / 1000);// 速率: 1 , 容量: 5 , 现在时间秒值: now ,请求令牌数:1return Arrays.asList("1", "5", now, "1");}@Overridepublic void afterPropertiesSet() throws Exception {Jedis jedis = new Jedis("127.0.0.1", 6379);ClassPathResource classPathResource = new ClassPathResource("/META-INF/script/redis_limiter.lua");byte[] buffer = new byte[(int)classPathResource.getFile().length()];classPathResource.getInputStream().read(buffer);scriptLua = jedis.scriptLoad(new String(buffer));}
}
mvc配置
package com.xzq.config;
@Component
public class MvcConfig implements WebMvcConfigurer {@Autowiredprivate LimiterIntercepter limiterIntercepter;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(limiterIntercepter);}
}
三、Jemeter压测工具测试
使用Jemeter压测工具进行测试
可以看到一秒内只允许进入五个请求,因为令牌桶容量为5,初始是值就是5,后面就是一秒进一个,因为我们设置得速率是1。
Jedis使用lua脚本完成令牌桶限流相关推荐
- 基于Redis和 Lua 实现分布式令牌桶限流
rpc-tech-stack 系列的实践文章 ~ 本文属于限流话题. 限流是一个很大的话题,准备把其中的所有限流器都实现一遍,以此也算全都写过了,到时候再用也不至于会心虚,毕竟真实写完成过.本文主要讲 ...
- 可能要用心学高并发核心编程,限流原理与实战,分布式令牌桶限流
实战:分布式令牌桶限流 本节介绍的分布式令牌桶限流通过Lua+Java结合完成,首先在Lua脚本中完成限流的计算,然后在Java代码中进行组织和调用. 分布式令牌桶限流Lua脚本 分布式令牌桶限流Lu ...
- ASP.NET Core中使用令牌桶限流
在限流时一般会限制每秒或每分钟的请求数,简单点一般会采用计数器算法,这种算法实现相对简单,也很高效,但是无法应对瞬时的突发流量. 比如限流每秒100次请求,绝大多数的时间里都不会超过这个数,但是偶尔某 ...
- 【秒杀系统】零基础上手秒杀系统(二):令牌桶限流 + 再谈超卖
前言 本文是秒杀系统的第二篇,通过实际代码讲解,帮助你快速的了解秒杀系统的关键点,上手实际项目. 本篇主要讲解接口限流措施,接口限流其实定义也非常广,接口限流本身也是系统安全防护的一种措施,暂时列举这 ...
- 什么是限流?为什么会限流呢?常见的限流算法【固定窗口限流、滑动窗口限流、漏桶限流、令牌桶限流】是什么呢?
什么是限流?为什么会限流呢?常见的限流算法[固定窗口限流.滑动窗口限流.漏桶限流.令牌桶限流]是什么呢? 什么是限流? 为什么会限流? 1. 固定窗口限流算法 1.1 什么是固定窗口限流算法 1.2 ...
- 十三水算法php_基于PHP+Redis令牌桶限流
一 .场景描述 在开发接口服务器的过程中,为了防止客户端对于接口的滥用,保护服务器的资源, 通常来说我们会对于服务器上的各种接口进行调用次数的限制.比如对于某个 用户,他在一个时间段(interval ...
- 令牌桶限流之redis-cell的安装,使用,详解
简言 1. redis使用有序集合zset也能实现简单的限流,但是只能处理几十,几百的量级,因为zset需要记录每一条信息,很占据空间.要想处理更大数量级的限流,必须使用其他方法 2. 通常的限流算法 ...
- rateLimiter令牌桶限流算法
RateLimiter是guava提供的基于令牌桶算法的实现类,可以非常简单的完成限流特技,并且根据系统的实际情况来调整生成token的速率. 通常可应用于抢购限流防止冲垮系统:限制某接口.服务单位时 ...
- php限制接口访问次数_令牌桶限流思路分享(PHP+Redis实现机制)
一 .场景描述 在开发接口服务器的过程中,为了防止客户端对于接口的滥用,保护服务器的资源, 通常来说我们会对于服务器上的各种接口进行调用次数的限制.比如对于某个 用户,他在一个时间段(interval ...
最新文章
- 协同办公工具解决了什么问题?
- ICRA2019 | 用于移动设备的双目立体匹配
- 教你如何保养iphone电池
- 决心开始写博,坚持!
- 【codeforces】【比赛题解】#940 CF Round #466 (Div. 2)
- 华为鸿蒙2.0操作页面,华为鸿蒙2.0开面界面确认,这一变化你可懂
- AndroidStudio_安卓原生开发_请求网络图片并解析成BitMap_异步处理_在UI线程执行_利用AsyncTask---Android原生开发工作笔记146
- 【Spring学习笔记-MVC-1.3】消息转换器HttpMessageConverter
- poj1200 Crazy Search(hash)
- SylixOS USB Gadget层介绍
- Dropout抑制过拟合与超参数选择
- UIcollectionView 加入尾部视图
- 鼠标移入通过时间控制实现两个不同步的动画效果
- 在苹果Mac上格式化USB闪存驱动器
- node2vec: 图数据的嵌入方法
- 常用BUG管理工具系统
- Altium和 Cadence Allegro 画的PCB导入Slwave
- 12帧跑步动画分解图_今天给大家分享一个跑步动画教程和注意事项!希望有所帮助!...
- 《阵列信号处理及MATLAB实现》绪论、矩阵代数相关内容总结笔记
- 男士不得不看的21种经典拍照姿势
热门文章
- Programing Exercise 4:Neural Networks Learning
- 阿里云购买域名到icp备案
- 除尘机器人毕业_一种除尘机器人的制作方法
- 并行算法设计与性能优化 刘文志 第2章 现代处理器特性
- PostgreSQL数据库TableAM——HeapAM Parallel table scan
- 【solidity】函数修饰器(Function Modifiers)
- 最简单网站视频加速方法
- IDEA 2018 3.4 激活破解方法
- linux上redis升级(将 Redis 3.0.7 升级到 5.0.0版本)
- Meterpreter渗透测试入门