基于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 相关文章推荐
redis 查看所有的key方式
May 07 Redis
基于Redis位图实现用户签到功能
May 08 Redis
Redis延迟队列和分布式延迟队列的简答实现
May 13 Redis
redis requires ruby version2.2.2的解决方案
Jul 15 Redis
springboot使用Redis作缓存使用入门教程
Jul 25 Redis
Redis读写分离搭建的完整步骤
Sep 14 Redis
基于Redis结合SpringBoot的秒杀案例详解
Oct 05 Redis
Redis的字符串是如何实现的
Oct 24 Redis
在Centos 8.0中安装Redis服务器的教程详解
Mar 21 Redis
基于Redis6.2.6版本部署Redis Cluster集群的问题
Apr 01 Redis
关于Redis的主从复制及哨兵问题
Jun 16 Redis
Redis Lua脚本实现ip限流示例
Jul 15 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
56.com视频采集接口程序(PHP)
2007/09/22 PHP
php 动态执行带有参数的类方法
2009/04/10 PHP
CodeIgniter采用config控制的多语言实现根据浏览器语言自动转换功能
2014/07/18 PHP
php实现基于微信公众平台开发SDK(demo)扩展的方法
2014/12/22 PHP
php简单生成随机数的方法
2015/07/30 PHP
PHP终止脚本运行三种实现方法详解
2020/09/01 PHP
从新浪弄下来的全屏广告代码 与使用说明
2007/03/15 Javascript
jQuery 连续列表实现代码
2009/12/21 Javascript
到处都是jQuery选择器的年代 不了解它们的性能,行吗
2012/06/18 Javascript
漂亮的jquery提示效果(仿腾讯弹出层)
2013/02/05 Javascript
jQuery实现指定区域外单击关闭指定层的方法【经典】
2016/06/22 Javascript
Node.js使用Koa搭建 基础项目
2018/01/08 Javascript
jQuery中可见性过滤器简单用法示例
2018/03/31 jQuery
vue + any-touch实现一个iscroll 实现拖拽和滑动动画效果
2019/04/08 Javascript
细说Vue组件的服务器端渲染的过程
2019/05/30 Javascript
[06:06]2018DOTA2亚洲邀请赛主赛事第四日战况回顾 全明星赛欢乐上演
2018/04/07 DOTA
[30:37]【全国守擂赛】第三周擂主赛 Dark Knight vs. Leopard Gaming
2020/05/04 DOTA
[59:15]完美世界DOTA2联赛PWL S2 LBZS vs FTD.C 第一场 11.20
2020/11/20 DOTA
python实现通过pil模块对图片格式进行转换的方法
2015/03/24 Python
Python获取运行目录与当前脚本目录的方法
2015/06/01 Python
Python实现根据IP地址和子网掩码算出网段的方法
2015/07/30 Python
windows系统下Python环境搭建教程
2017/03/28 Python
python实现决策树分类算法
2017/12/21 Python
Python统计python文件中代码,注释及空白对应的行数示例【测试可用】
2018/07/25 Python
Python实现图片转字符画的代码实例
2019/02/22 Python
使用Python实现将list中的每一项的首字母大写
2019/06/11 Python
详解django实现自定义manage命令的扩展
2019/08/13 Python
Python3爬虫带上cookie的实例代码
2020/07/28 Python
英国标志性生活方式品牌:Skinnydip London
2019/12/15 全球购物
初中毕业生的自我评价
2014/03/03 职场文书
副总经理岗位职责
2014/03/16 职场文书
被委托人身份证明
2015/08/07 职场文书
教你怎么用python selenium实现自动化测试
2021/05/27 Python
Python语言规范之Pylint的详细用法
2021/06/24 Python
动漫APP软件排行榜前十名,半次元上榜,第一款由腾讯公司推出
2022/03/18 杂记
游戏《东方异文石:爱亚利亚黎明》正式版发布
2022/04/03 其他游戏