IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> Jedis使用lua脚本完成令牌桶限流 -> 正文阅读

[大数据]Jedis使用lua脚本完成令牌桶限流

Jedis使用lua脚本完成令牌桶限流

一、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 then
    currentValue=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 then
    last_tokens=capacity
end
local last_time=tonumber(redis.call('get',time_key))
if last_time==nil then
    last_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) then
    acuire=1
    current_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;
    @Override
    public 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 ,请求令牌数:1
        return Arrays.asList("1", "5", now, "1");
    }

    @Override
    public 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 {

    @Autowired
    private LimiterIntercepter limiterIntercepter;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(limiterIntercepter);
    }
}

三、Jemeter压测工具测试

使用Jemeter压测工具进行测试
在这里插入图片描述
可以看到一秒内只允许进入五个请求,因为令牌桶容量为5,初始是值就是5,后面就是一秒进一个,因为我们设置得速率是1。

  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2021-11-27 09:57:56  更:2021-11-27 09:59:34 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:52:55-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码