springmvc限流攔截器的示例代碼
限流器算法
目前常用限流器算法為兩種:令牌桶算法和漏桶算法,主要區(qū)別在于:漏桶算法能夠強(qiáng)行限制請求速率,平滑突發(fā)請求,而令牌桶算法在限定平均速率的情況下,允許一定量的突發(fā)請求
下面是從網(wǎng)上找到的兩張算法圖示,就很容易區(qū)分這兩種算法的特性了
漏桶算法
令牌桶算法
針對接口來說,一般會(huì)允許處理一定量突發(fā)請求,只要求限制平均速率,所以令牌桶算法更加常見。
令牌桶算法工具RateLimiter
目前本人常用的令牌桶算法實(shí)現(xiàn)類當(dāng)屬google guava的RateLimiter,guava不僅實(shí)現(xiàn)了令牌桶算法,還有緩存、新的集合類、并發(fā)工具類、字符串處理類等等。是一個(gè)強(qiáng)大的工具集
RateLimiter api可以查看并發(fā)編程網(wǎng)guava RateLimiter的介紹
RateLimiter源碼分析
RateLimiter默認(rèn)情況下,最核心的屬性有兩個(gè)nextFreeTicketMicros,下次可獲取令牌時(shí)間,storedPermits桶內(nèi)令牌數(shù)。
判斷是否可獲取令牌:
每次獲取令牌的時(shí)候,根據(jù)桶內(nèi)令牌數(shù)計(jì)算最快下次能獲取令牌的時(shí)間nextFreeTicketMicros,判斷是否可以獲取資源時(shí),只要比較nextFreeTicketMicros和當(dāng)前時(shí)間就可以了,so easy
獲取令牌操作:
對于獲取令牌,根據(jù)nextFreeTicketMicros和當(dāng)前時(shí)間計(jì)算出新增的令牌數(shù),寫入當(dāng)前令牌桶令牌數(shù),重新計(jì)算nextFreeTicketMicros,桶內(nèi)還有令牌,則寫入當(dāng)前時(shí)間,并減少本次請求獲取的令牌數(shù)。
如同java的AQS類一樣,RateLimiter的核心在tryAcquire方法
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) {
//嘗試獲取資源最多等待時(shí)間
long timeoutMicros = max(unit.toMicros(timeout), 0);
//檢查獲取資源數(shù)目是否正確
checkPermits(permits);
long microsToWait;
//加鎖
synchronized (mutex()) {
//當(dāng)前時(shí)間
long nowMicros = stopwatch.readMicros();
//判斷是否可以在timeout時(shí)間內(nèi)獲取資源
if (!canAcquire(nowMicros, timeoutMicros)) {
return false;
} else {
//可獲取資源,對資源進(jìn)行重新計(jì)算,并返回當(dāng)前線程需要休眠時(shí)間
microsToWait = reserveAndGetWaitLength(permits, nowMicros);
}
}
//休眠
stopwatch.sleepMicrosUninterruptibly(microsToWait);
return true;
}
判斷是否可獲取令牌:
private boolean canAcquire(long nowMicros, long timeoutMicros) {
//最早可獲取資源時(shí)間-等待時(shí)間<=當(dāng)前時(shí)間 方可獲取資源
return queryEarliestAvailable(nowMicros) - timeoutMicros <= nowMicros;
}
RateLimiter默認(rèn)實(shí)現(xiàn)類的queryEarliestAvailable是取成員變量nextFreeTicketMicros
獲取令牌并計(jì)算需要等待時(shí)間操作:
final long reserveAndGetWaitLength(int permits, long nowMicros) {
//獲取下次可獲取時(shí)間
long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
//計(jì)算當(dāng)前線程需要休眠時(shí)間
return max(momentAvailable - nowMicros, 0);
}
final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
//重新計(jì)算桶內(nèi)令牌數(shù)storedPermits
resync(nowMicros);
long returnValue = nextFreeTicketMicros;
//本次消耗的令牌數(shù)
double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
//重新計(jì)算下次可獲取時(shí)間nextFreeTicketMicros
double freshPermits = requiredPermits - storedPermitsToSpend;
long waitMicros =
storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
+ (long) (freshPermits * stableIntervalMicros);
this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
//減少桶內(nèi)令牌數(shù)
this.storedPermits -= storedPermitsToSpend;
return returnValue;
}
實(shí)現(xiàn)簡單的spring mvc限流攔截器
實(shí)現(xiàn)一個(gè)HandlerInterceptor,在構(gòu)造方法中創(chuàng)建一個(gè)RateLimiter限流器
public SimpleRateLimitInterceptor(int rate) {
if (rate > 0)
globalRateLimiter = RateLimiter.create(rate);
else
throw new RuntimeException("rate must greater than zero");
}
在preHandle調(diào)用限流器的tryAcquire方法,判斷是否已經(jīng)超過限制速率
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!globalRateLimiter.tryAcquire()) {
LoggerUtil.log(request.getRequestURI()+"請求超過限流器速率");
return false;
}
return true;
}
在dispatcher-servlet.xml中配置限流攔截器
<mvc:interceptors>
<!--限流攔截器-->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="limit.SimpleRateLimitInterceptor">
<constructor-arg index="0" value="${totalRate}"/>
</bean>
</mvc:interceptor>
</mvc:interceptors>
復(fù)雜版本的spring mvc限流攔截器
使用Properties傳入攔截的url表達(dá)式->速率rate
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="limit.RateLimitInterceptor">
<!--單url限流-->
<property name="urlProperties">
<props>
<prop key="/get/{id}">1</prop>
<prop key="/post">2</prop>
</props>
</property>
</bean>
</mvc:interceptor>
為每個(gè)url表達(dá)式創(chuàng)建一個(gè)對應(yīng)的RateLimiter限流器。url表達(dá)式則封裝為org.springframework.web.servlet.mvc.condition.PatternsRequestCondition。PatternsRequestCondition是springmvc 的DispatcherServlet中用來匹配請求和Controller的類,可以判斷請求是否符合這些url表達(dá)式。
在攔截器preHandle方法中
//當(dāng)前請求路徑
String lookupPath = urlPathHelper.getLookupPathForRequest(request);
//迭代所有url表達(dá)式對應(yīng)的PatternsRequestCondition
for (PatternsRequestCondition patternsRequestCondition : urlRateMap.keySet()) {
//進(jìn)行匹配
List<String> matches = patternsRequestCondition.getMatchingPatterns(lookupPath);
if (!matches.isEmpty()) {
//匹配成功的則獲取對應(yīng)限流器的令牌
if (urlRateMap.get(patternsRequestCondition).tryAcquire()) {
LoggerUtil.log(lookupPath + " 請求匹配到" + Joiner.on(",").join(patternsRequestCondition.getPatterns()) + "限流器");
} else {
//獲取令牌失敗
LoggerUtil.log(lookupPath + " 請求超過" + Joiner.on(",").join(patternsRequestCondition.getPatterns()) + "限流器速率");
return false;
}
}
}
具體的實(shí)現(xiàn)類
請見github
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
SpringCloud Gateway中斷言路由和過濾器的使用詳解
這篇文章主要介紹了SpringCloud Gateway中斷言路由和過濾器的使用,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-04-04
Spring Boot中使用Spring-Retry重試框架的實(shí)現(xiàn)
本文主要介紹了Spring Boot中使用Spring-Retry重試框架的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04
Java抽象類和普通類區(qū)別、 數(shù)組跟List的區(qū)別解析
這篇文章主要介紹了Java抽象類和普通類區(qū)別、 數(shù)組跟List的區(qū)別,在這里需要注意List是一個(gè)接口,不能直接實(shí)例化,需要使用具體的實(shí)現(xiàn)類來創(chuàng)建對象,本文結(jié)合示例代碼介紹的非常詳細(xì),需要的朋友參考下吧2023-09-09
mybatis中mapper.xml文件的常用屬性及標(biāo)簽講解
這篇文章主要介紹了mybatis中mapper.xml文件的常用屬性及標(biāo)簽講解,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
java計(jì)算值所占的百分比,結(jié)果為100%問題
這篇文章主要介紹了java計(jì)算值所占的百分比,結(jié)果為100%問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11

