Springboot项目接口限流实现方案

 更新时间:2024年08月19日 10:22:53   作者:一棵星  
这篇文章主要介绍了Springboot项目接口限流实现方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

系统限流要求

  • 系统总并发数限制,如设置1000,表示该系统接口每秒可以请求1000次
  • 自定义系统接口请求并发数,也可以不加限流设置,如设置100,表示每秒可以请求100次该接口
  • 指定接口IP请求并发数,如设置1,表示每秒该IP可以请求1次该接口

实现思路

  • 每秒系统总并发数限流实现,可以使用拦截器或过滤器,来处理系统总并发数限流的实现
  • 自定义系统接口请求并发数和指定接口IP请求并发数的实现,可以使用自定义注解和切面,来处理自定义系统接口请求并发数的实现
  • 可以使用Redisson RRateLimiter组件实现具体限流逻辑
  • 自定义业务异常类,当请求数超出请求限制时,来打断业务

核心代码

1.接口限流注解

package com.ocean.angel.tool.annotation;

import java.lang.annotation.*;

/**
 * 接口限流注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ApiLimiting {

    // 接口请求限制数
    int apiRequestLimit() default 200;

    // 接口请求IP限制数
    int apiIpLimit() default 1;
}

2.接口限流切面

package com.ocean.angel.tool.aspect;

import com.ocean.angel.tool.annotation.ApiLimiting;
import com.ocean.angel.tool.constant.ApiLimitingTypeEnum;
import com.ocean.angel.tool.constant.ResultCode;
import com.ocean.angel.tool.dto.ApiLimitingData;
import com.ocean.angel.tool.exception.BusinessException;
import com.ocean.angel.tool.util.RateLimiterKeyUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.reflect.Method;

/**
 * 接口限流切面
 */
@Slf4j
@Aspect
@Component
public class ApiLimitingAspect {

    @Resource
    private RedissonClient redissonClient;

    @Pointcut("@annotation(com.ocean.angel.tool.annotation.ApiLimiting)")
    public void apiLimitingAspect() {}

    @Before(value = "apiLimitingAspect()")
    public void apiLimiting(JoinPoint joinPoint) {
        ApiLimitingData apiLimitingData = getApiLimitData(joinPoint);
        rateLimiterHandler(redissonClient, apiLimitingData);
    }

    /**
     * API 限流逻辑处理
     */
    private void rateLimiterHandler(RedissonClient redissonClient, ApiLimitingData apiLimitingData) {

        if(apiLimitingData.getApiIpLimit() > 0) {

            // 获取RRateLimiter实例
            RRateLimiter rateLimiter = redissonClient.getRateLimiter(getRateLimiterKey(apiLimitingData, ApiLimitingTypeEnum.API_IP_LIMIT));

            // RRateLimiter初始化
            if(!RateLimiterKeyUtil.contains(getRateLimiterKey(apiLimitingData, ApiLimitingTypeEnum.API_IP_LIMIT))) {
                rateLimiter.trySetRate(RateType.OVERALL, apiLimitingData.getApiIpLimit(), 1, RateIntervalUnit.SECONDS);
            }

            // 超出接口请求IP限流设置,打断业务
            if (!rateLimiter.tryAcquire()) {
                log.info("接口{}超出IP请求限制, 时间:{}",apiLimitingData.getMethodName(), System.currentTimeMillis());
                throw new BusinessException(ResultCode.BEYOND_RATE_LIMIT);
            }
        }

        if(apiLimitingData.getApiRequestLimit() > 0) {

            RRateLimiter rateLimiter = redissonClient.getRateLimiter(getRateLimiterKey(apiLimitingData, ApiLimitingTypeEnum.API_REQUEST_LIMIT));

            if(!RateLimiterKeyUtil.contains(getRateLimiterKey(apiLimitingData, ApiLimitingTypeEnum.API_REQUEST_LIMIT))) {
                rateLimiter.trySetRate(RateType.OVERALL, apiLimitingData.getApiRequestLimit(), 1, RateIntervalUnit.SECONDS);
            }

            // 超出接口请求限流设置,打断业务
            if (!rateLimiter.tryAcquire()) {
                log.info("接口{}超出请求限制, 时间:{}",apiLimitingData.getMethodName(), System.currentTimeMillis());
                throw new BusinessException(ResultCode.BEYOND_RATE_LIMIT);
            }
        }
    }

    /**
     * 组装ApiLimitingData
     */
    private ApiLimitingData getApiLimitData(JoinPoint joinPoint) {

        ApiLimitingData apiLimitingData = new ApiLimitingData();

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        apiLimitingData.setMethodName(method.getName());

        ApiLimiting apiLimiting = method.getAnnotation(ApiLimiting.class);
        apiLimitingData.setApiRequestLimit(apiLimiting.apiRequestLimit());
        apiLimitingData.setApiIpLimit(apiLimiting.apiIpLimit());

        return apiLimitingData;
    }

    /**
     * RateLimiter Key
     */
    private String getRateLimiterKey(ApiLimitingData apiLimitingData, ApiLimitingTypeEnum apiLimitingTypeEnum) {
        return apiLimitingData.getMethodName() + "_" + apiLimitingTypeEnum.getCode();
    }
}

3.系统接口限流拦截器

package com.ocean.angel.tool.interceptor;

import com.ocean.angel.tool.constant.ResultCode;
import com.ocean.angel.tool.exception.BusinessException;
import com.ocean.angel.tool.util.RateLimiterKeyUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
public class ApiLimitingInterceptor implements HandlerInterceptor {

    private final static String API_TOTAL_LIMIT = "apiTotalLimit";

    // 系统每秒请求总数,30表示每秒最多处理30个请求
    private final static int API_TOTAL_LIMIT_NUMBER = 30;
    private final RedissonClient redissonClient;

    public ApiLimitingInterceptor(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        RRateLimiter rateLimiter = redissonClient.getRateLimiter(API_TOTAL_LIMIT);

        if(!RateLimiterKeyUtil.contains(API_TOTAL_LIMIT)) {
            rateLimiter.trySetRate(RateType.OVERALL, API_TOTAL_LIMIT_NUMBER, 1, RateIntervalUnit.SECONDS);
        }

        // 超出系统接口总请求数限制,打断业务
        if (!rateLimiter.tryAcquire()) {
            log.info("超出系统接口总请求数限制, 时间:{}", System.currentTimeMillis());
            throw new BusinessException(ResultCode.BEYOND_RATE_LIMIT);
        }
        return true;
    }
}

4.接口自定义注解配置

@ApiLimiting(apiRequestLimit = 5, apiIpLimit = 1)
@GetMapping("/limited/resource")
public ResultBean<?> limitedResource() {
    return ResultBean.success();
}

限流方案演示

下载源代码,github源码连接

修改application.yml和redission.yml,关于redis的相关配置

启动项目,调用http://localhost:8090/test/limited/resource接口,截图如下:

保持项目启动状态,运行com.ocean.angel.tool.ApplicationTests.contextLoads()方法,截图如下:

使用指南

修改系统总请求数限制

调整系统接口限流参数

本文使用Redisson RRateLimiter组件实现具体限流逻辑,小伙伴们可以自己去手写具体限流功能(可以参考Redission的限流相关的数据结构)

注意:

小伙伴们如果修改系统限流的配置,需要先删除redis里面的限流数据(如上图),不然修改不会生效。

本文使用以1秒为单位进行系统并发数控制,小伙伴可以根据需要自己去修改,如下:

rateLimiter.trySetRate(RateType.OVERALL, apiLimitingData.getApiIpLimit(), 1, RateIntervalUnit.SECONDS)

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 后端将数据转化为json字符串传输的方法详解

    后端将数据转化为json字符串传输的方法详解

    这篇文章主要给大家介绍了关于后端将数据转化为json字符串传输的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • springboot实战权限管理功能图文步骤附含源码

    springboot实战权限管理功能图文步骤附含源码

    这篇文章主要为大家介绍了springboot实战权限管理功能图文步骤及示例源码,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • 如何利用 Either 和 Option 进行函数式错误处理

    如何利用 Either 和 Option 进行函数式错误处理

    这篇文章主要介绍了如何利用 Either 和 Option 进行函数式错误处理。在 Java 中,错误的处理在传统上由异常以及创建和传播异常的语言支持进行。但是,如果不存在结构化异常处理又如何呢?,需要的朋友可以参考下
    2019-06-06
  • Spring中的集合注入代码实例

    Spring中的集合注入代码实例

    这篇文章主要介绍了Spring中的集合注入代码实例,集合注入是指在Spring框架中,通过配置文件或注解的方式将集合类型的数据注入到Bean中,集合类型包括List、Set、Map和Properties等,需要的朋友可以参考下
    2023-11-11
  • Spring Boot 2.7.6整合redis与低版本的区别

    Spring Boot 2.7.6整合redis与低版本的区别

    这篇文章主要介绍了Spring Boot 2.7.6整合redis与低版本的区别,文中补充介绍了SpringBoot各个版本使用Redis之间的区别实例讲解,需要的朋友可以参考下
    2023-02-02
  • Spring基于AspectJ的AOP开发案例解析

    Spring基于AspectJ的AOP开发案例解析

    这篇文章主要介绍了Spring的基于AspectJ的AOP开发,AspectJ是一个基于Java语言的AOP框架,使用AspectJ需要导入Spring AOP和AspectJ相关jar包,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-05-05
  • Java内存溢出案例模拟和原理分析过程

    Java内存溢出案例模拟和原理分析过程

    这篇文章主要介绍了Java内存溢出案例模拟和原理分析过程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • servlet实现图片上传功能

    servlet实现图片上传功能

    这篇文章主要为大家详细介绍了servlet实现图片的上传,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-09-09
  • Java中线程Thread的特点及使用

    Java中线程Thread的特点及使用

    这篇文章主要介绍了Java中线程的特点及使用,线程是进程的组成部分,一个进程可以拥有多个线程,而一个线程必须拥有一个父进程,那么线程该如何使用,让我们一起来看看吧
    2023-04-04
  • Java中的clone()和Cloneable接口实例

    Java中的clone()和Cloneable接口实例

    这篇文章主要介绍了Java中的clone()和Cloneable接口实例,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11

最新评论