Springboot項(xiàng)目接口限流實(shí)現(xiàn)方案
系統(tǒng)限流要求
- 系統(tǒng)總并發(fā)數(shù)限制,如設(shè)置1000,表示該系統(tǒng)接口每秒可以請(qǐng)求1000次
- 自定義系統(tǒng)接口請(qǐng)求并發(fā)數(shù),也可以不加限流設(shè)置,如設(shè)置100,表示每秒可以請(qǐng)求100次該接口
- 指定接口IP請(qǐng)求并發(fā)數(shù),如設(shè)置1,表示每秒該IP可以請(qǐng)求1次該接口
實(shí)現(xiàn)思路
- 每秒系統(tǒng)總并發(fā)數(shù)限流實(shí)現(xiàn),可以使用攔截器或過(guò)濾器,來(lái)處理系統(tǒng)總并發(fā)數(shù)限流的實(shí)現(xiàn)
- 自定義系統(tǒng)接口請(qǐng)求并發(fā)數(shù)和指定接口IP請(qǐng)求并發(fā)數(shù)的實(shí)現(xiàn),可以使用自定義注解和切面,來(lái)處理自定義系統(tǒng)接口請(qǐng)求并發(fā)數(shù)的實(shí)現(xiàn)
- 可以使用Redisson RRateLimiter組件實(shí)現(xiàn)具體限流邏輯
- 自定義業(yè)務(wù)異常類,當(dāng)請(qǐng)求數(shù)超出請(qǐng)求限制時(shí),來(lái)打斷業(yè)務(wù)
核心代碼
1.接口限流注解
package com.ocean.angel.tool.annotation;
import java.lang.annotation.*;
/**
* 接口限流注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ApiLimiting {
// 接口請(qǐng)求限制數(shù)
int apiRequestLimit() default 200;
// 接口請(qǐng)求IP限制數(shù)
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實(shí)例
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);
}
// 超出接口請(qǐng)求IP限流設(shè)置,打斷業(yè)務(wù)
if (!rateLimiter.tryAcquire()) {
log.info("接口{}超出IP請(qǐng)求限制, 時(shí)間:{}",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);
}
// 超出接口請(qǐng)求限流設(shè)置,打斷業(yè)務(wù)
if (!rateLimiter.tryAcquire()) {
log.info("接口{}超出請(qǐng)求限制, 時(shí)間:{}",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.系統(tǒng)接口限流攔截器
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";
// 系統(tǒng)每秒請(qǐng)求總數(shù),30表示每秒最多處理30個(gè)請(qǐng)求
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);
}
// 超出系統(tǒng)接口總請(qǐng)求數(shù)限制,打斷業(yè)務(wù)
if (!rateLimiter.tryAcquire()) {
log.info("超出系統(tǒng)接口總請(qǐng)求數(shù)限制, 時(shí)間:{}", 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,關(guān)于redis的相關(guān)配置
啟動(dòng)項(xiàng)目,調(diào)用http://localhost:8090/test/limited/resource接口,截圖如下:

保持項(xiàng)目啟動(dòng)狀態(tài),運(yùn)行com.ocean.angel.tool.ApplicationTests.contextLoads()方法,截圖如下:

使用指南
修改系統(tǒng)總請(qǐng)求數(shù)限制
調(diào)整系統(tǒng)接口限流參數(shù)
本文使用Redisson RRateLimiter組件實(shí)現(xiàn)具體限流邏輯,小伙伴們可以自己去手寫(xiě)具體限流功能(可以參考Redission的限流相關(guān)的數(shù)據(jù)結(jié)構(gòu))

注意:
小伙伴們?nèi)绻薷南到y(tǒng)限流的配置,需要先刪除redis里面的限流數(shù)據(jù)(如上圖),不然修改不會(huì)生效。
本文使用以1秒為單位進(jìn)行系統(tǒng)并發(fā)數(shù)控制,小伙伴可以根據(jù)需要自己去修改,如下:
rateLimiter.trySetRate(RateType.OVERALL, apiLimitingData.getApiIpLimit(), 1, RateIntervalUnit.SECONDS)
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
springboot實(shí)戰(zhàn)權(quán)限管理功能圖文步驟附含源碼
如何利用 Either 和 Option 進(jìn)行函數(shù)式錯(cuò)誤處理
Spring Boot 2.7.6整合redis與低版本的區(qū)別
Spring基于AspectJ的AOP開(kāi)發(fā)案例解析
Java中的clone()和Cloneable接口實(shí)例

