基于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 27 Redis
Java Socket实现Redis客户端的详细说明
May 26 Redis
Redis基于Bitmap实现用户签到功能
Jun 20 Redis
厉害!这是Redis可视化工具最全的横向评测
Jul 15 Redis
redis 存储对象的方法对比分析
Aug 02 Redis
使用redis实现延迟通知功能(Redis过期键通知)
Sep 04 Redis
在Centos 8.0中安装Redis服务器的教程详解
Mar 21 Redis
 Redis 串行生成顺序编码的方法实现
Apr 03 Redis
Redis基本数据类型哈希Hash常用操作命令
Jun 01 Redis
Redis主从复制操作和配置详情
Sep 23 Redis
Redis配置外网可访问(redis远程连接不上)的方法
Dec 24 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面向对象分析设计的61条军规小结
2010/07/17 PHP
php设计模式 Builder(建造者模式)
2011/06/26 PHP
php的ddos攻击解决方法
2015/01/08 PHP
PHP实现163邮箱自动发送邮件
2016/03/29 PHP
Smarty实现页面静态化(生成HTML)的方法
2016/05/23 PHP
PHP开发APP端微信支付功能
2017/02/17 PHP
清除网页历史记录,屏蔽后退按钮!
2008/12/22 Javascript
初学js插入节点appendChild insertBefore使用方法
2011/07/04 Javascript
JS读取cookies信息(记录用户名)
2012/01/10 Javascript
jquery 插件学习(一)
2012/08/06 Javascript
js猜数字小游戏的简单实现代码
2013/07/02 Javascript
如何在JavaScript中实现私有属性的写类方式(二)
2013/12/04 Javascript
深入理解javascript变量声明
2014/11/20 Javascript
NodeJS的Promise的用法解析
2016/05/05 NodeJs
详解JavaScript设计模式开发中的桥接模式使用
2016/05/18 Javascript
JavaScript数据结构之二叉树的删除算法示例
2017/04/13 Javascript
Vuejs入门教程之Vue生命周期,数据,手动挂载,指令,过滤器
2017/04/19 Javascript
JavaScript用200行代码制作打飞机小游戏实例
2017/06/21 Javascript
利用JavaScript的%做隔行换色的实例
2017/11/25 Javascript
Vue指令v-for遍历输出JavaScript数组及json对象的常见方式小结
2019/02/11 Javascript
深入了解Hybrid App技术的相关知识
2019/07/17 Javascript
js获取本日、本周、本月的时间代码
2020/02/01 Javascript
用Python实现换行符转换的脚本的教程
2015/04/16 Python
python实现将读入的多维list转为一维list的方法
2018/06/28 Python
opencv python 2D直方图的示例代码
2018/07/20 Python
Python脚本利用adb进行手机控制的方法
2019/07/08 Python
使用虚拟环境打包python为exe 文件的方法
2019/08/29 Python
pytorch 图像中的数据预处理和批标准化实例
2020/01/15 Python
利用纯html5绘制出来的一款非常漂亮的时钟
2015/01/04 HTML / CSS
加拿大床上用品、家居装饰、厨房和浴室产品购物网站:Linen Chest
2018/06/05 全球购物
Diesel美国网上商店:意大利牛仔时装品牌
2020/12/10 全球购物
Java和Javasciprt的区别
2012/09/02 面试题
保安岗位职责
2014/02/21 职场文书
亮剑观后感500字
2015/06/05 职场文书
如何才能写好调研报告?
2019/07/03 职场文书
mysql分组后合并显示一个字段的多条数据方式
2022/01/22 MySQL