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)文章
Mybatis-Plus或PageHelper多表分頁查詢總條數(shù)不對問題的解決方法
PageHelper 這個(gè)插件用了很多次了,今天使用的時(shí)候才遇到一個(gè)問題,這篇文章主要給大家介紹了關(guān)于Mybatis-Plus或PageHelper多表分頁查詢總條數(shù)不對問題的解決方法,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08
java+selenium實(shí)現(xiàn)滑塊驗(yàn)證
現(xiàn)在越來越多的網(wǎng)站都使用采用滑塊驗(yàn)證來作為驗(yàn)證機(jī)制,用于判斷用戶是否為人類而不是機(jī)器人,本文就將利用java和selenium實(shí)現(xiàn)滑塊驗(yàn)證,希望對大家有所幫助2023-12-12
springmvc和js前端的數(shù)據(jù)傳遞和接收方式(兩種)
本文介紹了springmvc和js前端的數(shù)據(jù)傳遞和接收方式(兩種),詳細(xì)的介紹了兩種方式,一種是json格式傳遞,另一種是Map傳遞,具有一定的參考價(jià)值,有興趣的可以了解一下2017-12-12
Java線程池內(nèi)部任務(wù)出異常后如何知道是哪個(gè)線程出了異常
在Java線程池中當(dāng)任務(wù)執(zhí)行過程中發(fā)生異常時(shí),識(shí)別發(fā)生異常的任務(wù)所對應(yīng)的線程是一個(gè)常見的調(diào)試需求,這篇文章主要介紹了Java線程池內(nèi)部任務(wù)出異常后如何知道是哪個(gè)線程出了異常的相關(guān)資料,需要的朋友可以參考下2026-01-01

