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

作者:Jae1995 时间:2022-04-11 09:15:45 

为什么要限流

系统在设计的时候,我们会有一个系统的预估容量,长时间超过系统能承受的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)第一次发送,正常返回结果

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

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

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

来源:https://www.cnblogs.com/jae-tech/p/16625091.html

标签:SpringBoot,自定义注解,接口限流
0
投稿

猜你喜欢

  • C#.net实现在Winform中从internet下载文件的方法

    2023-09-13 18:04:15
  • java对象拷贝常见面试题及应答汇总

    2022-01-08 00:01:52
  • Hibernatede 一对多映射配置方法(分享)

    2021-08-27 02:26:32
  • Java 中如何使用 stream 流

    2021-10-09 01:13:37
  • Android自定义输入法软键盘

    2023-04-20 05:51:54
  • springBoot 打war包 程序包com.sun.istack.internal不存在的问题及解决方案

    2023-03-28 06:12:40
  • Java多线程 两阶段终止模式Two-Phase Termination Patter

    2023-11-29 04:47:04
  • Spring Cloud 配置中心内容加密的配置方法

    2023-02-11 09:05:15
  • Mybatis延迟加载和缓存深入讲解

    2022-06-02 15:50:43
  • C++调用C#的DLL程序实现方法

    2023-02-04 13:58:16
  • Java动态代理分析及理解

    2021-10-21 14:59:58
  • Java使用自定义注解实现为事件源绑定事件监听器操作示例

    2023-06-21 01:17:55
  • springcloud项目占用内存好几个G导致服务器崩溃的问题

    2023-03-30 09:54:25
  • java实现简单的webservice方式

    2023-11-25 03:59:58
  • Android数据存储方式操作模式解析

    2023-07-27 02:47:05
  • flutter窗口初始和绘制流程详析

    2023-08-17 21:07:30
  • Unity打开淘宝app并跳转到商品页面功能的实现方法

    2023-06-17 01:05:18
  • Java8 用Lambda表达式给List集合排序的实现

    2023-02-05 17:27:09
  • 快速定位Java 内存OOM的问题

    2022-05-26 00:19:38
  • mybatis @Alias注解在类上的使用方式(推荐)

    2023-11-20 00:30:03
  • asp之家 软件编程 m.aspxhome.com