基于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安装启动及常见数据类型
Apr 14 Redis
redis限流的实际应用
Apr 24 Redis
redis三种高可用方式部署的实现
May 11 Redis
你真的了解redis为什么要提供pipeline功能
Jun 22 Redis
解析redis hash应用场景和常用命令
Aug 04 Redis
Redis+Lua脚本实现计数器接口防刷功能(升级版)
Feb 12 Redis
Redis调用Lua脚本及使用场景快速掌握
Mar 16 Redis
redis数据结构之压缩列表
Mar 21 Redis
Redis安装使用RedisJSON模块的方法
Mar 23 Redis
Redis 哨兵机制及配置实现
Mar 25 Redis
Redis数据同步之redis shake的实现方法
Apr 21 Redis
Redis实现分布式锁的五种方法详解
Jun 14 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
php Memcache 中实现消息队列
2009/11/24 PHP
PHP实现批量上传单个文件
2015/12/29 PHP
Yii CGridView用法实例详解
2016/07/12 PHP
windows下的WAMP环境搭建图文教程(推荐)
2017/07/27 PHP
Thinkphp5框架简单实现钩子(Hook)行为的方法示例
2019/09/03 PHP
将list转换为json失败的原因
2013/12/17 Javascript
jquery分页插件jpaginate在IE中不兼容问题
2014/04/22 Javascript
javascript正则表达式参数/g与/i及/gi的使用指南
2014/08/27 Javascript
网页中表单按回车就自动提交的问题的解决方案
2014/11/03 Javascript
js验证真实姓名与身份证号是否匹配
2015/10/13 Javascript
jQuery Validate插件实现表单强大的验证功能
2015/12/18 Javascript
4种JavaScript实现简单tab选项卡切换的方法
2016/01/06 Javascript
js获取iframe中的window对象的实现方法
2016/05/20 Javascript
微信小程序 时间格式化(util.formatTime(new Date))详解
2016/11/16 Javascript
基于vue.js实现侧边菜单栏
2017/03/20 Javascript
webstorm添加vue.js支持的方法教程
2017/07/05 Javascript
Web技术实现移动监测的介绍
2017/09/18 Javascript
JavaScript使用Math.random()生成简单的验证码
2019/01/21 Javascript
vue 解决遍历对象显示的顺序不对问题
2019/11/07 Javascript
javascript 对象 与 prototype 原型用法实例分析
2019/11/11 Javascript
详解Numpy中的广播原则/机制
2018/09/20 Python
对Python Pexpect 模块的使用说明详解
2019/02/14 Python
Django 如何使用日期时间选择器规范用户的时间输入示例代码详解
2020/05/22 Python
Python Dict找出value大于某值或key大于某值的所有项方式
2020/06/05 Python
使用html2canvas.js实现页面截图并显示或上传的示例代码
2018/12/18 HTML / CSS
纽约JewelryAffairs珠宝店:精细金银时尚首饰
2017/02/05 全球购物
英国高街电视:High Street TV
2018/05/22 全球购物
烹调加工管理制度
2014/02/04 职场文书
2014年幼儿园植树节活动方案
2014/03/02 职场文书
冬季安全检查方案
2014/05/23 职场文书
乡镇精神文明建设汇报材料
2014/08/15 职场文书
党的群众路线教育实践活动心得体会(乡镇)
2014/11/03 职场文书
2016年法制宣传月活动总结
2016/04/01 职场文书
Python机器学习之逻辑回归
2021/05/11 Python
对Keras自带Loss Function的深入研究
2021/05/25 Python
游戏《东方异文石:爱亚利亚黎明》正式版发布
2022/04/03 其他游戏