基于redis+lua进行限流的方法


Posted in Redis onJuly 23, 2022

1,首先我们redis有很多限流的算法(比如:令牌桶,计数器,时间窗口)等,但是都有一定的缺点,令牌桶在单项目中相对来说比较稳定,但是在分布式集群里面缺显的不那么友好,这时候,在分布式里面进行限流的话,我们则可以使用redis+lua脚本进行限流,能抗住亿级并发

2,下面说说lua+redis进行限流的做法
开发环境:idea+redis+lua
第一:
打开idea的插件市场,然后搜索lua,点击右边的安装,然后安装好了,重启即可

基于redis+lua进行限流的方法

第二:写一个自定义限流注解

package com.sport.sportcloudmarathonh5.config;

import java.lang.annotation.*;

/**
 * @author zdj
 * @version 1.0.0
 * @description 自定义注解实现分布式限流
 */
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisLimitStream {
    /**
     * 请求限制,一秒内可以允许好多个进入(默认一秒可以支持100个)
     * @return
     */
    int reqLimit() default 1000;

    /**
     * 模块名称
     * @return
     */
    String reqName() default "";
}

第三:在指定的方法上面添加该注解

/**
     * 压测接口
     * @return
     */
    @Login(isLogin = false)
    @RedisLimitStream(reqName = "名额秒杀", reqLimit = 1000)
    @ApiOperation(value = "压测接口", notes = "压测接口", httpMethod = "GET")
    @RequestMapping(value = "/pressure", method = RequestMethod.GET)
    public ResultVO<Object> pressure(){
        return ResultVO.success("抢购成功!");
    }

第四:添加一个拦截器对访问的方法在访问之前进行拦截:

package com.sport.sportcloudmarathonh5.config;

import com.alibaba.fastjson.JSONObject;
import com.sport.sportcloudmarathonh5.service.impl.RedisService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

/**
 * @author zdj
 * @version 1.0.0
 * @description MyRedisLimiter注解的切面类
 */
@Aspect
@Component
public class RedisLimiterAspect {
    private final Logger logger = LoggerFactory.getLogger(RedisLimitStream.class);
    /**
     * 当前响应请求
     */
    @Autowired
    private HttpServletResponse response;

    /**
     * redis服务
     */
    @Autowired
    private RedisService redisService;

    /**
     * 执行redis的脚本文件
     */
    @Autowired
    private RedisScript<Boolean> rateLimitLua;

    /**
     * 对所有接口进行拦截
     */
    @Pointcut("execution(public * com.sport.sportcloudmarathonh5.controller.*.*(..))")
    public void pointcut(){}

    /**
     * 对切点进行继续处理
     */
    @Around("pointcut()")
    public Object process(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        //使用反射获取RedisLimitStream注解
        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
        //没有添加限流注解的方法直接放行
        RedisLimitStream redisLimitStream = signature.getMethod().getDeclaredAnnotation(RedisLimitStream.class);
        if(ObjectUtils.isEmpty(redisLimitStream)){
            return proceedingJoinPoint.proceed();
        }

        //List设置Lua的KEYS[1]
        List<String> keyList = new ArrayList<>();
        keyList.add("ip:" + (System.currentTimeMillis() / 1000));

        //获取注解上的参数,获取配置的速率
        //List设置Lua的ARGV[1]
        int value = redisLimitStream.reqLimit();

        // 调用Redis执行lua脚本,未拿到令牌的,直接返回提示
        boolean acquired = redisService.execute(rateLimitLua, keyList, value);
        logger.info("执行lua结果:" + acquired);
        if(!acquired){
            this.limitStreamBackMsg();
            return null;
        }

        //获取到令牌,继续向下执行
        return proceedingJoinPoint.proceed();
    }

    /**
     * 被拦截的人,提示消息
     */
    private void limitStreamBackMsg() {
        response.setHeader("Content-Type", "text/html;charset=UTF8");
        PrintWriter writer = null;
        try {
            writer = response.getWriter();
            writer.println("{\"code\":503,\"message\":\"当前排队人较多,请稍后再试!\",\"data\":\"null\"}");
            writer.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (writer != null) {
                writer.close();
            }
        }
    }
}

第五:写个配置类,在启动的时候将我们的lua脚本代码加载到redisscript中

package com.sport.sportcloudmarathonh5.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.script.DefaultRedisScript;

/**
 * @author zdj
 * @version 1.0.0
 * @description 实现redis的编码方式
 */
@Configuration
public class RedisConfiguration {

    /**
     * 初始化将lua脚本加载到redis脚本中
     * @return
     */
    @Bean
    public DefaultRedisScript loadRedisScript() {
        DefaultRedisScript redisScript = new DefaultRedisScript();
        redisScript.setLocation(new ClassPathResource("limit.lua"));
        redisScript.setResultType(Boolean.class);
        return redisScript;
    }
}

第六:redis执行lua的方法

/**
     * 执行lua脚本
     * @param redisScript lua源代码脚本
     * @param keyList
     * @param value
     * @return
     */
    public boolean execute(RedisScript<Boolean> redisScript, List<String> keyList, int value) {
        return redisTemplate.execute(redisScript, keyList, String.valueOf(value));
    }

第七:在resources目录下面新加一个lua脚本文件,将下面代码拷贝进去即可:

local key = KEYS[1] --限流KEY(一秒一个)
local limit = tonumber(ARGV[1]) --限流大小
local current = tonumber(redis.call('get', key) or "0")
if current + 1 > limit then --如果超出限流大小
    return false
else --请求数+1,并设置2秒过期
    redis.call("INCRBY", key, "1")
    redis.call("expire", key, "2")
end
return true

基于redis+lua进行限流的方法

最后执行即可:
可以使用jemster进行测试:

基于redis+lua进行限流的方法

到此这篇关于基于redis+lua进行限流的文章就介绍到这了,更多相关redis lua限流内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Redis 相关文章推荐
Redis5之后版本的高可用集群搭建的实现
Apr 27 Redis
Redis数据结构之链表与字典的使用
May 11 Redis
基于Redis实现分布式锁的方法(lua脚本版)
May 12 Redis
详解缓存穿透击穿雪崩解决方案
May 28 Redis
浅谈Redis中的RDB快照
Jun 29 Redis
Redis 彻底禁用RDB持久化操作
Jul 09 Redis
Redis如何实现分布式锁
Aug 23 Redis
Redis入门教程详解
Aug 30 Redis
高并发下Redis如何保持数据一致性(避免读后写)
Mar 18 Redis
redis数据一致性的实现示例
Mar 18 Redis
Redis 哨兵机制及配置实现
Mar 25 Redis
redis调用二维码时的不断刷新排查分析
Apr 01 Redis
Redis过期数据是否会被立马删除
Jul 23 #Redis
如何使用注解方式实现 Redis 分布式锁
Jul 23 #Redis
redis lua限流算法实现示例
Redis Lua脚本实现ip限流示例
Jul 15 #Redis
redis protocol通信协议及使用详解
Jul 15 #Redis
Redis sentinel哨兵集群的实现步骤
Jul 15 #Redis
Redis唯一ID生成器的实现
Jul 07 #Redis
You might like
法压式咖啡之制作法
2021/03/03 冲泡冲煮
PHP 如何向 MySQL 发送数据
2006/10/09 PHP
php简单提示框alert封装函数
2010/08/08 PHP
解析PHP可变函数的经典用法
2013/06/20 PHP
DOM 基本方法
2009/07/18 Javascript
jquery mobile changepage的三种传参方法介绍
2013/09/13 Javascript
jquery通过select列表选择框对表格数据进行过滤示例
2014/05/07 Javascript
jQuery中[attribute*=value]选择器用法实例
2014/12/31 Javascript
jQuery中prev()方法用法实例
2015/01/08 Javascript
JavaScript的设计模式经典之代理模式
2016/02/24 Javascript
jquery获取复选框checkbox的值实现方法
2016/05/30 Javascript
angular route中使用resolve在uglify压缩后问题解决
2016/09/21 Javascript
深入理解Javascript箭头函数中的this
2017/02/13 Javascript
JS常见创建类的方法小结【工厂方式,构造器方式,原型方式,联合方式等】
2017/04/01 Javascript
AngularJS 最常用的八种功能(基础知识)
2017/06/26 Javascript
微信小程序实现弹出层效果
2020/05/26 Javascript
JS实现简单的文字无缝上下滚动功能示例
2019/06/22 Javascript
vue实现短信验证码登录功能(流程详解)
2019/12/10 Javascript
JS数组方法reverse()用法实例分析
2020/01/18 Javascript
30分钟搭建Python的Flask框架并在上面编写第一个应用
2015/03/30 Python
Django用户认证系统 User对象解析
2019/08/02 Python
Python datetime包函数简单介绍
2019/08/28 Python
Python基于WordCloud制作词云图
2019/11/29 Python
通过实例解析Python调用json模块
2019/12/11 Python
Python利用逻辑回归模型解决MNIST手写数字识别问题详解
2020/01/14 Python
Python在centos7.6上安装python3.9的详细教程(默认python版本为2.7.5)
2020/10/15 Python
澳大利亚巧克力花束和礼品网站:Tastebuds
2019/03/15 全球购物
GUESS Factory加拿大:牛仔裤、服装及配饰
2019/09/20 全球购物
使用索引(Index)有哪些需要考虑的因素
2016/10/19 面试题
《画家乡》教学反思
2014/04/22 职场文书
优秀毕业生的求职信
2014/07/21 职场文书
银行主办会计岗位职责
2014/08/13 职场文书
维稳工作情况汇报
2014/10/27 职场文书
小学运动会加油词
2015/07/18 职场文书
Python基础教程,Python入门教程(超详细)
2021/06/24 Python
PyTorch device与cuda.device用法
2022/04/03 Python