SpringBoot使用自定義注解+AOP+Redis實(shí)現(xiàn)接口限流的實(shí)例代碼
為什么要限流
系統(tǒng)在設(shè)計(jì)的時(shí)候,我們會(huì)有一個(gè)系統(tǒng)的預(yù)估容量,長時(shí)間超過系統(tǒng)能承受的TPS/QPS閾值,系統(tǒng)有可能會(huì)被壓垮,最終導(dǎo)致整個(gè)服務(wù)不可用。為了避免這種情況,我們就需要對接口請求進(jìn)行限流。
所以,我們可以通過對并發(fā)訪問請求進(jìn)行限速或者一個(gè)時(shí)間窗口內(nèi)的的請求數(shù)量進(jìn)行限速來保護(hù)系統(tǒng)或避免不必要的資源浪費(fèi),一旦達(dá)到限制速率則可以拒絕服務(wù)、排隊(duì)或等待。
限流背景
系統(tǒng)有一個(gè)獲取手機(jī)短信驗(yàn)證碼的接口,因?yàn)槭情_放接口,所以為了避免用戶不斷的發(fā)送請求獲取驗(yàn)證碼,防止惡意刷接口的情況發(fā)生,于是用最簡單的計(jì)數(shù)器方式做了限流,限制每個(gè)IP每分鐘只能請求一次,然后其他每個(gè)手機(jī)號(hào)的時(shí)間窗口限制則是通過業(yè)務(wù)邏輯進(jìn)行判斷。一般一些接口訪問量比較大的,可能會(huì)壓垮系統(tǒng)的,則需要加入流量限制!如:秒殺等...
實(shí)現(xiàn)限流
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;
/**
* 限流時(shí)間,單位秒
*/
int time() default 60;
/**
* 限流次數(shù)
*/
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); //請求進(jìn)來,對應(yīng)的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("網(wǎng)絡(luò)繁忙,請稍候再試");
}
}
/**
* 獲取限流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、寫一個(gè)簡單的接口進(jìn)行測試
@RestController
public class TestController {
@RateLimiter(time = 60, count = 1, limitType = LimitType.IP, limitMsg = "一分鐘內(nèi)只能請求一次,請稍后重試")
@GetMapping("/hello")
public ResultMsg hello() {
return ResultMsg.success("Hello World!");
}
}5、全局異常攔截
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 業(yè)務(wù)異常
*/
@ExceptionHandler(ServiceRuntimeException.class)
public ResultMsg handleServiceException(ServiceRuntimeException e, HttpServletRequest request)
{
return ResultMsg.error(e.getMessage());
}
/**
* 系統(tǒng)異常
*/
@ExceptionHandler(Exception.class)
public ResultMsg handleException(Exception e, HttpServletRequest request)
{
return ResultMsg.error("系統(tǒng)異常");
}
}6、接口測試
1)第一次發(fā)送,正常返回結(jié)果

2)一分鐘內(nèi)第二次發(fā)送,返回錯(cuò)誤,限流提示

好了,大功告成啦
還有其他的限流方式,如滑動(dòng)窗口限流方式(比計(jì)數(shù)器更嚴(yán)謹(jǐn))、令牌桶等...,有興趣的小伙伴可以學(xué)習(xí)一下
附源碼
https://gitee.com/jae_1995/ratelimiter
到此這篇關(guān)于SpringBoot使用自定義注解+AOP+Redis實(shí)現(xiàn)接口限流的文章就介紹到這了,更多相關(guān)SpringBoot接口限流內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java封裝公共Result結(jié)果返回類的實(shí)現(xiàn)
在使用Java開發(fā)接口請求中,我們需要對請求進(jìn)行進(jìn)行統(tǒng)一返回值,這時(shí)候我們自己封裝一個(gè)統(tǒng)一的Result返回類,本文主要介紹了Java封裝公共Result結(jié)果返回類的實(shí)現(xiàn),感興趣的可以了解一下2023-01-01
淺談java實(shí)現(xiàn)redis的發(fā)布訂閱(簡單易懂)
本篇文章主要介紹了淺談java實(shí)現(xiàn) redis的發(fā)布訂閱(簡單易懂),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-03-03
Idea如何使用Fast Request接口調(diào)試
這篇文章主要介紹了Idea如何使用Fast Request接口調(diào)試問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11
Spring?Boot?3.4.3?基于?Spring?WebFlux?實(shí)現(xiàn)?SSE?功能(代碼示例)
Spring Boot 3.4.3 結(jié)合Spring WebFlux實(shí)現(xiàn)SSE 功能,為實(shí)時(shí)數(shù)據(jù)推送提供了優(yōu)雅的解決方案,通過本文的步驟,你可以快速搭建一個(gè)基于事件驅(qū)動(dòng)的后端服務(wù),滿足實(shí)時(shí)通知或監(jiān)控等需求,感興趣的朋友一起看看吧2025-04-04
使用Java實(shí)現(xiàn)HTTP和HTTPS代理服務(wù)詳解
這篇文章主要為大家詳細(xì)介紹了如何使用Java實(shí)現(xiàn)HTTP和HTTPS代理服務(wù),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-04-04
Springboot實(shí)現(xiàn)MQTT通信的示例代碼
本文主要介紹了Springboot實(shí)現(xiàn)MQTT通信的示例代碼,包含了MQTT協(xié)議的特點(diǎn)和工作原理等,具有一定的參考價(jià)值,感興趣的可以了解一下2025-01-01
使用FeignClient調(diào)用遠(yuǎn)程服務(wù)時(shí)整合本地的實(shí)現(xiàn)方法
這篇文章主要介紹了使用FeignClient調(diào)用遠(yuǎn)程服務(wù)時(shí)整合本地的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03

