首页 > 其他分享 >简单的滑动窗口限流接口

简单的滑动窗口限流接口

时间:2024-08-15 12:58:23浏览次数:10  
标签:String maxCount private 限流 接口 import 滑动 param

简单的滑动窗口限流接口

1.需求

我们公司的流程部分使用了好几个版本的流程服务,当前修改为activiti5.5,那么原有的流程部分则进行了停止,但是历史流程部分还是需要提供查询,当前功能只需要流程历史三个月前数据查询使用即可,所以部分代码写死了只处理流程三个月历史信息查询。

比如近三个数据,这部分数据则需要先查询老流程wfms数据,还要查询新生成的activiti数据,其中wfms历史数据量非常大,两部分查询数据还需要进行整合导致接口查询效率过低,那么现在需要一个限流机制,想要同一时间段内最多5人查询,多出的人查询则提示当前查询人数过多,请稍后再查,等查询的5人当中有人查询结束,则其他人可以再次进行查询,这个功能就是一个简单的时间窗口限流功能。

2.具体实现过程

此功能主要实现的方式是通过redisZSet功能实现,具体使用的是 ZRANGEBYSCORE

他的主要语法为 ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count],意思是返回指定分数的成员。分数在 min max 之间,返回的成员按照 分数 从小到大排列,比如获取key对应score分数在0~100之内的数据,简单例子如下:

ZRANGEBYSCORE key 0 100

我们的时间窗口正是使用此功能, 我们使用功能的url作为rediskey,使用时间戳作为score分数, 那么判定一段时间内还有多少个该功能请求未完成可以使用 redisTemplate.opsForZSet().rangeByScore(key, currentTime - timeWindow, currentTime);获取,再获取返回值的size即可。具体实现过程如下:

  • 获取请求信息,判定是否添加时间窗口限流注解,没有则正常执行,添加进行限流校验
  • 切面拦截请求信息,判定是否首次添加,首次添加则redis注解生成Zset,key为url,value为uuid,score为当前时间戳,如果不是首次添加,则判定 当前时间-timeWindow时间内Zset值的size是否大于初始设定5,如果大于则提示查询人数过多提示,否则再添加Zset值,key为url,value为uuid,score为当前时间戳。

3.代码部分

代码部分主要有四部分,具体内容如下:

  • 注解部分

  • 切面部分

  • 公共类部分

  • 调用部分

  • 注解代码部分实现过程

    package cn.git.common.limit;
    
    import java.lang.annotation.*;
    
    /**
     * @description: 滑动窗口限流接口
     * @program: bank-credit-sy
     * @author: lixuchun
     * @create: 2023-03-09 04:24:06
     */
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface ApiLimitSlidingWindow {
    
        /**
         * 限流枚举 默认流程三个月前跟踪列表
         * @return
         */
        SlidingWindowEnum slidingWindowEnum() default SlidingWindowEnum.WORKFLOW_TRACE_HIS_WINDOW;
    
    }
    
    
  • 切面部分

    package cn.git.common.limit;
    
    import cn.git.common.exception.ServiceException;
    import cn.git.common.log.LogOperateProperties;
    import cn.git.redis.RedisUtil;
    import cn.hutool.core.util.ObjectUtil;
    import cn.hutool.core.util.StrUtil;
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONObject;
    import io.swagger.annotations.ApiOperation;
    import lombok.extern.slf4j.Slf4j;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.AfterThrowing;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    
    import javax.servlet.http.HttpServletRequest;
    import java.lang.reflect.Method;
    
    /**
     * @description:
     * @program: bank-credit-sy
     * @author: lixuchun
     * @create: 2023-03-09 05:54:11
     */
    @Aspect
    @Slf4j
    @Component
    public class LimitSlidingWindowAspect {
    
        /**
         * SCORE_INFO
         */
        private static final ThreadLocal<ZsetScore> SCORE_INFO = new ThreadLocal<>();
    
        /**
         * 设置分数信息
         * @param zsetScore score信息
         */
        private void setZsetScoreInfo(ZsetScore zsetScore) {
            if (ObjectUtil.isNotNull(zsetScore)) {
                SCORE_INFO.set(zsetScore);
            }
        }
    
        /**
         * 获取分数信息
         * @return 加锁值信息
         */
        private ZsetScore getZsetScoreInfo() {
            return SCORE_INFO.get();
        }
    
        /**
         * 删除分数信息
         */
        private void removeZsetScoreInfo() {
            SCORE_INFO.remove();
        }
    
        @Autowired
        private RedisUtil redisUtil;
    
        /**
         * post请求标识
         */
        private static final String POST_FLAG = "POST";
    
        /**
         * get请求标识
         */
        private static final String GET_FLAG = "GET";
    
        /**
         * 跟踪列表数据
         */
        private static final String WORKFLOW_TRACE_URI = "/findTraceList";
    
        /**
         * 限流名称前缀
         */
        private static final String LIMIT_SLIDING_WINDOW_PREFIX = "DICS_CUS_SLIDING_WINDOW:";
    
        /**
         * 0 信贷系统 1 风险系统 2 储备系统
         */
        private static final String SYSTEM_FLAG = "0";
    
        /**
         * 是否三个月前标识
         * 0 否 1 是
         */
        private static final String IF_THREE_MONTH_AGO_FLAG = "1";
    
        /**
         * 是否替换maxCount标识 0否 1是
         */
        private static final String IF_SPECIAL_FLAG = "1";
    
        /**
         * 切面信息前置切面
         * @param joinPoint 切点
         * @param apiLimitSlidingWindow 枚举类型
         */
        @Before("@annotation(apiLimitSlidingWindow)")
        public void beforePointCut(JoinPoint joinPoint, ApiLimitSlidingWindow apiLimitSlidingWindow) {
            //  参数为空设置默认值
            SlidingWindowEnum slidingWindowEnum = apiLimitSlidingWindow.slidingWindowEnum();
            if (ObjectUtil.isNull(slidingWindowEnum)) {
                slidingWindowEnum = SlidingWindowEnum.WORKFLOW_TRACE_HIS_WINDOW;
            }
    
            // 接收到请求,记录请求内容
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
    
            // 获取服务接口名称
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
    
            // GET请求
            if (GET_FLAG.equals(request.getMethod())) {
                // 获取参数信息
                Object[] args = joinPoint.getArgs();
    
                // 当前如果流程跟踪列表,则需要根据参数判断是否进行限流处理
                if (request.getRequestURI().contains(WORKFLOW_TRACE_URI)) {
                    if (ObjectUtil.isNotNull(args[0])) {
                        JSONObject paramJSON = JSON.parseObject(JSONObject.toJSONString(args[0]));
                        String systemFlag = paramJSON.getString("systemFlag");
                        String ifThreeMonthsAgo = paramJSON.getString("ifThreeMonthsAgo");
                        // 如果是信贷系统查询三个月以前信息,则需要加入限流处理
                        boolean ifLoanThreeMonthAgo = IF_THREE_MONTH_AGO_FLAG.equals(ifThreeMonthsAgo)
                                && (StrUtil.isBlank(systemFlag) || SYSTEM_FLAG.equals(systemFlag));
                        if (ifLoanThreeMonthAgo) {
                            setAndCheckLimitSlidingWindow(slidingWindowEnum, request.getRequestURI(), method, null);
                        } else if (ObjectUtil.isNotNull(slidingWindowEnum.getSpecialMaxCount())) {
                            setAndCheckLimitSlidingWindow(slidingWindowEnum, request.getRequestURI(), method, IF_SPECIAL_FLAG);
                        }
                    }
                }
            } else {
                // POST请求
                setAndCheckLimitSlidingWindow(slidingWindowEnum, request.getRequestURI(), method, IF_SPECIAL_FLAG);
            }
        }
    
        /**
         * 后置切面,释放查询信息
         * @param joinPoint 切面
         * @param apiLimitSlidingWindow 时间窗口
         */
        @AfterReturning("@annotation(apiLimitSlidingWindow)")
        public void afterReturning(JoinPoint joinPoint, ApiLimitSlidingWindow apiLimitSlidingWindow) {
            // 获取排序分数信息
            ZsetScore zsetScore = SCORE_INFO.get();
            SCORE_INFO.remove();
            if (ObjectUtil.isNotNull(zsetScore)) {
                redisUtil.removeZset(zsetScore.getKey(), zsetScore.getValue());
            }
        }
    
        /**
         * 后置异常切面 捕获异常信息
         * @param joinPoint joinPoint
         * @param e 异常信息
         */
        @AfterThrowing(value = "@annotation(apiLimitSlidingWindow)" , throwing = LogOperateProperties.LOG_EXCEPTION)
        public void afterThrowing(JoinPoint joinPoint, ApiLimitSlidingWindow apiLimitSlidingWindow, Exception e) {
            // 获取排序分数信息
            ZsetScore zsetScore = SCORE_INFO.get();
            SCORE_INFO.remove();
            if (ObjectUtil.isNotNull(zsetScore)) {
                redisUtil.removeZset(zsetScore.getKey(), zsetScore.getValue());
            }
        }
    
        /**
         * 设置限流时间窗口信息
         * @param slidingWindowEnum 窗口信息
         * @param uri 请求路径信息
         * @param method 方法描述
         * @param ifSpecial 是否maxCount替换标识 0 否 1 是
         */
        private void setAndCheckLimitSlidingWindow(SlidingWindowEnum slidingWindowEnum, String uri, Method method, String ifSpecial) {
            // 获取时间窗口内最大count数量,时间窗口
            Integer maxCount = slidingWindowEnum.getMaxCount();
            if (IF_SPECIAL_FLAG.equals(ifSpecial) && ObjectUtil.isNotNull(slidingWindowEnum.getSpecialMaxCount())) {
                maxCount = slidingWindowEnum.getSpecialMaxCount();
            }
            Integer timeWindow = slidingWindowEnum.getTimeWindow();
    
            // 判断是否已经存在限流信息
            String slidingWindowKey = LIMIT_SLIDING_WINDOW_PREFIX.concat(uri);
            if (redisUtil.hasKey(slidingWindowKey)) {
                // 判断是否已经大于最大限流
                if (redisUtil.rangeByScore(slidingWindowKey, timeWindow) > maxCount) {
                    ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
                    String methodDesc = null;
                    if (ObjectUtil.isNotNull(apiOperation)) {
                        methodDesc = apiOperation.value();
                    }
                    log.error("服务请求地址[{}]请求人数过多!", uri);
                    if (StrUtil.isNotBlank(methodDesc)) {
                        throw new ServiceException(StrUtil.format("[{}]请求人数过多,请稍后再试!", methodDesc));
                    } else {
                        throw new ServiceException("当前功能请求人数过多,请稍后再试!");
                    }
                }
            }
            // 新增限流基数
            String scoreValue = redisUtil.addZset(slidingWindowKey);
    
            // 设置线程传递值
            ZsetScore zsetScore = new ZsetScore();
            zsetScore.setKey(slidingWindowKey);
            zsetScore.setValue(scoreValue);
            SCORE_INFO.set(zsetScore);
        }
    
    }
    
    
  • 公共类部分
    公共类部分包含枚举部分以及ZsetScore对象类

    package cn.git.common.limit;
    
    import lombok.Getter;
    
    /**
     * @description: 滑动窗口枚举类
     * @program: bank-credit-sy
     * @author: lixuchun
     * @create: 2023-03-09 04:25:57
     */
    @Getter
    public enum SlidingWindowEnum {
    
        /**
         * 限流枚举实例
         */
        WORKFLOW_TRACE_HIS_WINDOW("老流程三个月前老历史任务查询限流", 60000, 5, 30),
        WORKFLOW_NEW_TRACE_HIS_WINDOW("新流程三个月前老历史任务查询限流", 60000, 5)
        ;
    
        /**
         * 限流描述信息
         */
        private String desc;
    
        /**
         * 时间窗口(毫秒)
         */
        private Integer timeWindow;
    
        /**
         * 时间窗口内默认最大限流
         */
        private Integer maxCount;
    
        /**
         * 正常业务,特定maxCount信息
         */
        private Integer specialMaxCount;
    
        /**
         * 自定义限流器构造方法
         * @param desc 描述信息
         * @param timeWindow 时间窗口
         * @param maxCount 时间窗口内最大限流
         */
        SlidingWindowEnum(String desc, Integer timeWindow, Integer maxCount) {
            this.desc = desc;
            this.timeWindow = timeWindow;
            this.maxCount = maxCount;
        }
    
        /**
         * 自定义限流器构造方法
         * @param desc 描述信息
         * @param timeWindow 时间窗口
         * @param maxCount 时间窗口内最大限流
         * @param specialMaxCount 特定maxCount信息,依情况判定,代替maxCount
         */
        SlidingWindowEnum(String desc, Integer timeWindow, Integer maxCount, Integer specialMaxCount) {
            this.desc = desc;
            this.timeWindow = timeWindow;
            this.maxCount = maxCount;
            this.specialMaxCount = specialMaxCount;
        }
    
    }
    
    

    实体对象部分

    package cn.git.common.limit;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    /**
     * @description: zsetScore
     * @program: bank-credit-sy
     * @author: lixuchun
     * @create: 2023-03-13 02:35:58
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class ZsetScore {
    
        /**
         * 列key
         */
        private String key;
    
        /**
         * value值
         */
        private String value;
    
        /**
         * 分数
         */
        private long score;
    
    }
    
    
  • 调用部分
    调用部分很简单,直接使用窗口注解设定窗口枚举对象即可
    在这里插入图片描述

4. 测试部分

使用jemeter进行简单测,20个线程同时请求数据
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

标签:String,maxCount,private,限流,接口,import,滑动,param
From: https://blog.csdn.net/qq_19342829/article/details/141120298

相关文章

  • uniapp movable-area、movable-view实现上下滑动
    效果图: <movable-areaclass="move-content"@touchmove.stop><movable-viewclass="move-incontent":style="'height:'+movableHeight+'rpx'":y="initY"......
  • 【Leetcode 594 】 最长和谐子序列 —— 这是假的滑动窗口吧!
    和谐数组是指一个数组里元素的最大值和最小值之间的差别 正好是 1 。现在,给你一个整数数组 nums ,请你在所有可能的子序列中找到最长的和谐子序列的长度。数组的子序列是一个由数组派生出来的序列,它可以通过删除一些元素或不删除元素、且不改变其余元素的顺序而得到。示......
  • [nRF24L01+] 5. 数据和控制接口
    5.数据和控制接口5.1.特点管脚:IRQ(该信号为低电平有效信号,由三个可屏蔽中断源控制)CE(此信号为高电平,用于在RX或TX模式下激活芯片)CSN(SPI信号)SCK(SPI信号)MOSI(SPI信号)MISO(SPI信号)SPI:速率:0-10Mbps5.2.SPI命令5.2.1.命令格式<Commandword:MSBittoLSBit(onebyte......
  • 7.接口文档,JWT介绍
    【一】接口文档1)公司使用使用world编写MD共享文档第三方平台:如:https://www.showdoc.com.cn/公司自研2)主要内容如下说明变更记录全局错误相关接口简要描述请求URL请求方式参数返回示例返回参数说明备注3)字段生成(coreapi使用)安装pipinstal......
  • 腾讯微服务框架(TSF)-令牌桶限流的实现方式
    1为什么需要限流限流,也称流量控制。是指系统在面临高并发,或者大流量请求的情况下,限制新的请求对系统的访问,从而保证系统的稳定性。限流会导致部分用户请求处理不及时或者被拒,这就影响了用户体验。所以一般需要在系统稳定和用户体验之间平衡一下。举个生活的例子:比如我们的交......
  • openGauss怎么工作SQL函数接口读取逻辑解码结果?
    功能描述在openGauss中如果实现数据复制呢?可以通过数据迁移工具定期向目标数据库进行数据库的同步,说的定期,这就意味着这种方式不能满足数据实时复制的需求。在openGauss中为我们提供了逻辑解码功能,工作原理就是反解xlog,从而生成逻辑日志,在目标数据库中通过对逻辑进行解析......
  • 身份实名认证-身份证实名认证-身份证实名-实名认证-身份证二要素-身份证实名认证-身份
    身份证实名认证快证API接口,通常指的是一种用于快速验证用户身份证信息的接口服务。这种接口主要用于身份证二要素(姓名和身份证号码)的官方实名核验,通过实时联网技术对接权威数据源,迅速准确地验证用户身份证信息的真实性。以下是对该接口的详细解析:一、接口功能核心功能:验证用......
  • 身份证实名认证类接口怎么选择?JavaScript身份证三要素核验接口返回参数说明
    当我们在选择身份证实名认证接口的时候,首先要考虑的是接口的稳定性和可靠性,翔云身份证实名认证接口,一般是指通过身份证三要素:身份证号、姓名、证件人像核验的方式来对身份证真伪的一致性进行核验,且接口的部署方式简单便捷。翔云身份证核验接口返回参数说明序号 名称 类......
  • Java--抽象类与接口
    目录抽象类的概念1.什么是抽象(与具体类相对)2.为什么要抽象抽象类的好处抽象类和接口的区别抽象类的概念1.什么是抽象(与具体类相对)Java专门提供了一种机制,名为“抽象方法”。它属于一种不完整的方法,只含有一个声明,没有方法主体。下面是抽象方法声明时采用的语法:abstractvoidX......
  • QxOrm环境搭建以及接口编写
    1.常用ORM库比较2.QxOrm库编译集成2.1.下载地址https://www.qxorm.com/qxorm_en/home.html2.2.编译2.2.1.源码下载2.2.2.cmake编译2.2.3.打开QxOrm工程编译VisualStudio2015(v140)版本库2.2.4.编译好的库生成目录3.注册3.1.注册类其中传入的模......