Springboot項目接口限流實現(xiàn)方案
系統(tǒng)限流要求
- 系統(tǒng)總并發(fā)數(shù)限制,如設置1000,表示該系統(tǒng)接口每秒可以請求1000次
- 自定義系統(tǒng)接口請求并發(fā)數(shù),也可以不加限流設置,如設置100,表示每秒可以請求100次該接口
- 指定接口IP請求并發(fā)數(shù),如設置1,表示每秒該IP可以請求1次該接口
實現(xiàn)思路
- 每秒系統(tǒng)總并發(fā)數(shù)限流實現(xiàn),可以使用攔截器或過濾器,來處理系統(tǒng)總并發(fā)數(shù)限流的實現(xiàn)
- 自定義系統(tǒng)接口請求并發(fā)數(shù)和指定接口IP請求并發(fā)數(shù)的實現(xiàn),可以使用自定義注解和切面,來處理自定義系統(tǒng)接口請求并發(fā)數(shù)的實現(xiàn)
- 可以使用Redisson RRateLimiter組件實現(xiàn)具體限流邏輯
- 自定義業(yè)務異常類,當請求數(shù)超出請求限制時,來打斷業(yè)務
核心代碼
1.接口限流注解
package com.ocean.angel.tool.annotation; import java.lang.annotation.*; /** * 接口限流注解 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface ApiLimiting { // 接口請求限制數(shù) int apiRequestLimit() default 200; // 接口請求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實例 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限流設置,打斷業(yè)務 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); } // 超出接口請求限流設置,打斷業(yè)務 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.系統(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)每秒請求總數(shù),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); } // 超出系統(tǒng)接口總請求數(shù)限制,打斷業(yè)務 if (!rateLimiter.tryAcquire()) { log.info("超出系統(tǒng)接口總請求數(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)配置
啟動項目,調(diào)用http://localhost:8090/test/limited/resource接口,截圖如下:
保持項目啟動狀態(tài),運行com.ocean.angel.tool.ApplicationTests.contextLoads()方法,截圖如下:
使用指南
修改系統(tǒng)總請求數(shù)限制
調(diào)整系統(tǒng)接口限流參數(shù)
本文使用Redisson RRateLimiter組件實現(xiàn)具體限流邏輯,小伙伴們可以自己去手寫具體限流功能(可以參考Redission的限流相關(guān)的數(shù)據(jù)結(jié)構(gòu))
注意:
小伙伴們?nèi)绻薷南到y(tǒng)限流的配置,需要先刪除redis里面的限流數(shù)據(jù)(如上圖),不然修改不會生效。
本文使用以1秒為單位進行系統(tǒng)并發(fā)數(shù)控制,小伙伴可以根據(jù)需要自己去修改,如下:
rateLimiter.trySetRate(RateType.OVERALL, apiLimitingData.getApiIpLimit(), 1, RateIntervalUnit.SECONDS)
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
說一說java關(guān)鍵字final和transient
這篇文章主要和大家說一說java關(guān)鍵字final和transient,感興趣的小伙伴們可以參考一下2016-06-06淺談SpringMVC之視圖解析器(ViewResolver)
本篇文章主要介紹了淺談SpringMVC之視圖解析器(ViewResolver),具有一定的參考價值,有興趣的可以了解一下2017-08-08java通過isAccessAllowed方法實現(xiàn)訪問控制
在Web應用開發(fā)中,使用Apache Shiro框架的isAccessAllowed方法可以有效管理用戶的訪問權(quán)限,本文詳細解析了該方法的實現(xiàn)過程,包括用戶身份驗證、權(quán)限判斷和安全性分析,下面就一起來了解一下2024-09-09java實現(xiàn)在線預覽--poi實現(xiàn)word、excel、ppt轉(zhuǎn)html的方法
這篇文章主要介紹了java實現(xiàn)在線預覽--poi實現(xiàn)word、excel、ppt轉(zhuǎn)html的方法,本文需要引入poi的jar包給大家介紹的非常詳細,需要的朋友可以參考下2019-09-09