首页 > 数据库 >SpringBoot使用自定义注解+AOP+Redis实现接口限流

SpringBoot使用自定义注解+AOP+Redis实现接口限流

时间:2022-09-07 11:46:30浏览次数:107  
标签:ResultMsg SpringBoot 自定义 class 限流 key public rateLimiter

为什么要限流

系统在设计的时候,我们会有一个系统的预估容量,长时间超过系统能承受的TPS/QPS阈值,系统有可能会被压垮,最终导致整个服务不可用。为了避免这种情况,我们就需要对接口请求进行限流。

所以,我们可以通过对并发访问请求进行限速或者一个时间窗口内的的请求数量进行限速来保护系统或避免不必要的资源浪费,一旦达到限制速率则可以拒绝服务、排队或等待。 

 

限流背景

系统有一个获取手机短信验证码的接口,因为是开放接口,所以为了避免用户不断的发送请求获取验证码,防止恶意刷接口的情况发生,于是用最简单的计数器方式做了限流,限制每个IP每分钟只能请求一次,然后其他每个手机号的时间窗口限制则是通过业务逻辑进行判断。一般一些接口访问量比较大的,可能会压垮系统的,则需要加入流量限制!如:秒杀等...

 

实现限流

1、引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>        

 

2、自定义限流注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimiter
{
    /**
     * 限流key
     */
     String key() default Constants.RATE_LIMIT_KEY;

    /**
     * 限流时间,单位秒
     */
     int time() default 60;

    /**
     * 限流次数
     */
    int count() default 100;

    /**
     * 限流类型
     */
    LimitType limitType() default LimitType.DEFAULT;

    /**
     * 限流后返回的文字
     */
    String limitMsg() default "访问过于频繁,请稍候再试";
}

 

3、限流切面

@Aspect
@Component
public class RateLimiterAspect {

    private final static Logger log = LoggerFactory.getLogger(RateLimiterAspect.class);

    @Autowired
    private RedisUtils redisUtils;

    @Before("@annotation(rateLimiter)")
    public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable
    {
        int time = rateLimiter.time();
        int count = rateLimiter.count();
        long total = 1L;

        String combineKey = getCombineKey(rateLimiter, point);
        try
        {
            if(redisUtils.hasKey(combineKey)){
                total = redisUtils.incr(combineKey,1);  //请求进来,对应的key加1
                if(total > count)
                    throw new ServiceRuntimeException(rateLimiter.limitMsg());
            }else{
                redisUtils.set(combineKey,1,time);  //初始化key
            }
        }
        catch (ServiceRuntimeException e)
        {
            throw e;
        }
        catch (Exception e)
        {
            throw new ServiceRuntimeException("网络繁忙,请稍候再试");
        }
    }

    /**
     * 获取限流key
     * @param rateLimiter
     * @param point
     * @return
     */
    public String getCombineKey(RateLimiter rateLimiter, JoinPoint point)
    {
        StringBuffer stringBuffer = new StringBuffer(rateLimiter.key());
        if (rateLimiter.limitType() == LimitType.IP)
        {
            stringBuffer.append(IpUtils.getIpAddr(ServletUtils.getRequest())).append("-");
        }
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        Class<?> targetClass = method.getDeclaringClass();
        stringBuffer.append(targetClass.getName()).append("-").append(method.getName());
        return stringBuffer.toString();
    }



}

 

4、写一个简单的接口进行测试

@RestController
public class TestController {

    @RateLimiter(time = 60, count = 1, limitType = LimitType.IP, limitMsg = "一分钟内只能请求一次,请稍后重试")
    @GetMapping("/hello")
    public ResultMsg hello() {
        return ResultMsg.success("Hello World!");
    }
}

 

5、全局异常拦截

@RestControllerAdvice
public class GlobalExceptionHandler {
    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 业务异常
     */
    @ExceptionHandler(ServiceRuntimeException.class)
    public ResultMsg handleServiceException(ServiceRuntimeException e, HttpServletRequest request)
    {
        return ResultMsg.error(e.getMessage());
    }

    /**
     * 系统异常
     */
    @ExceptionHandler(Exception.class)
    public ResultMsg handleException(Exception e, HttpServletRequest request)
    {
        return ResultMsg.error("系统异常");
    }

}

 

6、接口测试

1)第一次发送,正常返回结果

 

2)一分钟内第二次发送,返回错误,限流提示

 

 

好了,大功告成啦

还有其他的限流方式,如滑动窗口限流方式(比计数器更严谨)、令牌桶等...,有兴趣的小伙伴可以学习一下

 

附源码

https://gitee.com/jae_1995/ratelimiter

 

标签:ResultMsg,SpringBoot,自定义,class,限流,key,public,rateLimiter
From: https://www.cnblogs.com/jae-tech/p/16625091.html

相关文章

  • SpringBoot常用注解
    SpringBoot常用注解1.@SpringBootApplicationspringBoot的基石,启动类@Configuration应许spring注册额外的bean或者导入其他配置类@EnableAutoConfiguration启用Sp......
  • java poi - excel cell 设置自定义颜色
    XSSFCellStylecellStyle=wb.createCellStyle();cellStyle.setFillForegroundColor(newXSSFColor(newColor(195,227,255)));cellStyle.setFillPattern(FillPatter......
  • zabbix自定义监控
    zabbix自定义监控自定义监控进程测试监控httpd,需要在监控端部署httpd,以方便监控``配置监控脚本#在监控端[root@localhost~]#dnf-yinstallhttpd[root@localhost......
  • Springboot定义全局异常类详解
    前言当我们在开发过程中,会因为一些异常程序出现500,如果直接显示给客户看,这样很不友好。并且对我们后期维护,排查bug很困难。准备1.创建一个SpringBoot项目,引入web依赖,......
  • zabbix自定义监控进程与日志
    zabbix自定义监控进程与日志目录zabbix自定义监控进程与日志zabbix自定义监控进程zabbix自定义监控日志zabbix自定义监控进程基于之前的邮箱告警,部署完成后,我们在zabbi......
  • 【WPF】自定义用登入界面 (C#) -从认证和授权说起。
    概要自定义如下界面登入界面WPF桌面软件。写代码时候要注意哪些事情呢?答案:认证和授权。  我们在桌面应用软件登入界面时,作为小白一般都是用明文密码登入软件然后就......
  • springboot官方文档解读
    官网地址:https://docs.spring.io/spring-boot/docs/2.7.3/reference/htmlsingle/1第一个springboot项目我们在一个路径下面创建pom.xml文件<?xmlversion="1.0"encod......
  • zabbix自定义监控进程和日志
    zabbix自定义监控进程和日志目录zabbix自定义监控进程和日志自定义监控进程配置监控脚本添加监控项添加触发器手动关闭httpd服务,触发报警自定义监控日志服务端和客户端操......
  • [安装配置] SpringBoot项目部署
    打包SpringBoot项目 部署方式一:手动部署1、将打包好的jar包上传到Linux服务器中mkdir-p/opt/java62/app2、前台启动SpringBoot应用编译jar包:java-jarhellowor......
  • 【uni-app】自定义导航栏/标题栏
    什么是自定义导航栏默认导航栏或原生导航栏是啥样的,你懂的。但,我想给导航栏加个背景图,比如这样: 这时候就需要自定义导航栏。自定义导航栏自定义导航栏的中心思想是: ......