基于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实现订单自动过期功能的示例代码
May 08 Redis
基于Redis延迟队列的实现代码
May 13 Redis
分布式锁为什么要选择Zookeeper而不是Redis?看完这篇你就明白了
May 21 Redis
详解Redis瘦身指南
May 26 Redis
浅谈Redis位图(Bitmap)及Redis二进制中的问题
Jul 15 Redis
Redis分布式锁Redlock的实现
Aug 07 Redis
Redis 持久化 RDB 与 AOF的执行过程
Nov 07 Redis
关于使用Redisson订阅数问题
Jan 18 Redis
redis数据一致性的实现示例
Mar 18 Redis
Redis 哨兵机制及配置实现
Mar 25 Redis
Redis基本数据类型Zset有序集合常用操作
Jun 01 Redis
redis lua限流算法实现示例
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
PHP中通过加号合并数组的一个简单方法分享
2011/01/27 PHP
解析php中获取系统信息的方法
2013/06/25 PHP
php制作简单模版引擎
2016/04/07 PHP
用JQuery 实现AJAX加载XML并解析的脚本
2009/07/25 Javascript
关于JavaScript定义类和对象的几种方式
2010/11/09 Javascript
jquery 实现上下滚动效果示例代码
2013/08/09 Javascript
教你如何使用PHP输出中文JSON字符串
2014/05/22 Javascript
JS创建对象的写法示例
2016/11/04 Javascript
js实现复选框的全选和取消全选效果
2017/01/03 Javascript
使用 Node.js 对文本内容分词和关键词抽取
2017/05/27 Javascript
Vue网页html转换PDF(最低兼容ie10)的思路详解
2017/08/24 Javascript
详解javascript 正则表达式之分组与前瞻匹配
2018/05/30 Javascript
angularJs中ng-model-options设置数据同步的方法
2018/09/30 Javascript
使用angular-cli webpack创建多个包的方法
2018/10/16 Javascript
vue实现图片预览组件封装与使用
2019/07/13 Javascript
原生javascript如何实现共享onload事件
2020/07/03 Javascript
vue移动端下拉刷新和上滑加载
2020/10/27 Javascript
Python压缩和解压缩zip文件
2015/02/14 Python
使用python画个小猪佩奇的示例代码
2018/06/06 Python
python进行两个表格对比的方法
2018/06/27 Python
python 读取视频,处理后,实时计算帧数fps的方法
2018/07/10 Python
Python3.4学习笔记之列表、数组操作示例
2019/03/01 Python
scrapy-splash简单使用详解
2021/02/21 Python
使用CSS媒体查询(Media Queries)和JavaScript判断浏览器设备类型的方法
2014/04/03 HTML / CSS
美的官方商城:Midea
2016/09/14 全球购物
Mio Skincare美国官网:身体紧致及孕期身体护理
2017/03/05 全球购物
罗兰·穆雷官网:Roland Mouret
2018/09/28 全球购物
Booking.com亚太地区:Booking.com APAC
2020/02/07 全球购物
武汉高蓝德国际.net机试
2016/06/24 面试题
财务经理的岗位职责
2013/12/17 职场文书
三爱活动实施方案
2014/03/19 职场文书
班子成员四风问题自我剖析材料
2014/09/29 职场文书
校园广播稿100字
2014/10/06 职场文书
医生见习报告范文
2014/11/03 职场文书
预备党员自我评价范文
2015/03/04 职场文书
酒会开场白大全
2015/06/01 职场文书