Springboot項(xiàng)目接口限流實(shí)現(xiàn)方案
系統(tǒng)限流要求
- 系統(tǒng)總并發(fā)數(shù)限制,如設(shè)置1000,表示該系統(tǒng)接口每秒可以請求1000次
- 自定義系統(tǒng)接口請求并發(fā)數(shù),也可以不加限流設(shè)置,如設(shè)置100,表示每秒可以請求100次該接口
- 指定接口IP請求并發(fā)數(shù),如設(shè)置1,表示每秒該IP可以請求1次該接口
實(shí)現(xiàn)思路
- 每秒系統(tǒng)總并發(fā)數(shù)限流實(shí)現(xiàn),可以使用攔截器或過濾器,來處理系統(tǒng)總并發(fā)數(shù)限流的實(shí)現(xiàn)
- 自定義系統(tǒng)接口請求并發(fā)數(shù)和指定接口IP請求并發(fā)數(shù)的實(shí)現(xiàn),可以使用自定義注解和切面,來處理自定義系統(tǒng)接口請求并發(fā)數(shù)的實(shí)現(xiàn)
- 可以使用Redisson RRateLimiter組件實(shí)現(xiàn)具體限流邏輯
- 自定義業(yè)務(wù)異常類,當(dāng)請求數(shù)超出請求限制時(shí),來打斷業(yè)務(wù)
核心代碼
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實(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); } // 超出接口請求IP限流設(shè)置,打斷業(yè)務(wù) if (!rateLimiter.tryAcquire()) { log.info("接口{}超出IP請求限制, 時(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); } // 超出接口請求限流設(shè)置,打斷業(yè)務(wù) if (!rateLimiter.tryAcquire()) { log.info("接口{}超出請求限制, 時(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)每秒請求總數(shù),30表示每秒最多處理30個(gè)請求 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è)務(wù) if (!rateLimiter.tryAcquire()) { log.info("超出系統(tǒ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)總請求數(shù)限制
調(diào)整系統(tǒng)接口限流參數(shù)
本文使用Redisson RRateLimiter組件實(shí)現(xiàn)具體限流邏輯,小伙伴們可以自己去手寫具體限流功能(可以參考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è)參考,也希望大家多多支持腳本之家。
- 詳解Springboot集成sentinel實(shí)現(xiàn)接口限流入門
- Spring Boot接口限流的常用算法及特點(diǎn)
- SpringBoot如何使用自定義注解實(shí)現(xiàn)接口限流
- SpringBoot Redis用注釋實(shí)現(xiàn)接口限流詳解
- SpringBoot使用Redis對(duì)用戶IP進(jìn)行接口限流的示例詳解
- SpringBoot使用Redis對(duì)用戶IP進(jìn)行接口限流的項(xiàng)目實(shí)踐
- SpringBoot整合resilience4j實(shí)現(xiàn)接口限流
- SpringBoot+Resilience4j實(shí)現(xiàn)接口限流的示例代碼
相關(guān)文章
說一說java關(guān)鍵字final和transient
這篇文章主要和大家說一說java關(guān)鍵字final和transient,感興趣的小伙伴們可以參考一下2016-06-06淺談SpringMVC之視圖解析器(ViewResolver)
本篇文章主要介紹了淺談SpringMVC之視圖解析器(ViewResolver),具有一定的參考價(jià)值,有興趣的可以了解一下2017-08-08java通過isAccessAllowed方法實(shí)現(xiàn)訪問控制
在Web應(yīng)用開發(fā)中,使用Apache Shiro框架的isAccessAllowed方法可以有效管理用戶的訪問權(quán)限,本文詳細(xì)解析了該方法的實(shí)現(xiàn)過程,包括用戶身份驗(yàn)證、權(quán)限判斷和安全性分析,下面就一起來了解一下2024-09-09java實(shí)現(xiàn)在線預(yù)覽--poi實(shí)現(xiàn)word、excel、ppt轉(zhuǎn)html的方法
這篇文章主要介紹了java實(shí)現(xiàn)在線預(yù)覽--poi實(shí)現(xiàn)word、excel、ppt轉(zhuǎn)html的方法,本文需要引入poi的jar包給大家介紹的非常詳細(xì),需要的朋友可以參考下2019-09-09Windows下gradle的安裝與配置的超詳細(xì)教程
這篇文章主要介紹了Windows下gradle的安裝與配置,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09