Java AOP實現(xiàn)自定義滑動窗口限流器方法詳解
滑動窗口算法
滑動窗口算法是一種廣泛應(yīng)用于計算機(jī)科學(xué)和數(shù)據(jù)分析中的數(shù)據(jù)流算法,特別適用于處理具有時間序列特性的數(shù)據(jù),如網(wǎng)絡(luò)流量監(jiān)控、速率限制、數(shù)據(jù)分析等領(lǐng)域。其核心思想是在一個固定大小的“窗口”內(nèi)對數(shù)據(jù)進(jìn)行統(tǒng)計分析,這個窗口會隨著數(shù)據(jù)的流入而向前滑動,始終保持最新一段時間內(nèi)的數(shù)據(jù)統(tǒng)計。
基本概念
- 窗口大小:滑動窗口有一個固定的尺寸,表示你關(guān)心的數(shù)據(jù)的時間范圍或數(shù)據(jù)數(shù)量。例如,如果你關(guān)注的是過去5分鐘內(nèi)的數(shù)據(jù),那么窗口大小就是5分鐘。
- 滑動/移動:隨著時間的推移或新數(shù)據(jù)的到來,窗口會不斷向前移動,丟棄最舊的數(shù)據(jù)點,同時納入最新的數(shù)據(jù)點,始終保持窗口內(nèi)數(shù)據(jù)的新鮮度。
- 數(shù)據(jù)處理:在窗口內(nèi)的數(shù)據(jù)會被用來進(jìn)行各種計算,比如求平均值、最大值、最小值、計數(shù)等,具體取決于應(yīng)用場景。
應(yīng)用實例
- 網(wǎng)絡(luò)流量控制:在網(wǎng)絡(luò)傳輸中,滑動窗口常用來控制發(fā)送速率,避免擁塞。TCP協(xié)議中的擁塞控制就采用了類似滑動窗口的機(jī)制來調(diào)整數(shù)據(jù)包的發(fā)送速率。
- 速率限制(Rate Limiting):在Web服務(wù)中,滑動窗口算法可以用來實現(xiàn)對API調(diào)用或其他請求的速率限制,確保服務(wù)不會因為過多的請求而過載。通過控制窗口期內(nèi)的請求總數(shù)或特定時間段內(nèi)的請求頻率,可以平滑系統(tǒng)負(fù)載。
- 交易監(jiān)控:在金融系統(tǒng)中,滑動窗口可用于監(jiān)控交易活動,比如檢測是否存在異常交易模式,通過分析一段時間內(nèi)的交易頻次和金額分布。
實現(xiàn)要點
- 數(shù)據(jù)結(jié)構(gòu)選擇:為了高效實現(xiàn)滑動窗口,通常使用隊列或哈希表等數(shù)據(jù)結(jié)構(gòu)來存儲窗口內(nèi)的數(shù)據(jù),便于快速插入和刪除元素。
- 窗口邊界處理:需要準(zhǔn)確地管理窗口的邊界,確保當(dāng)新數(shù)據(jù)到來時,能及時移除窗口最左邊(或最舊)的數(shù)據(jù)點,同時加入新數(shù)據(jù)點。
- 時間復(fù)雜度:理想情況下,滑動窗口算法的操作(如添加元素、移除元素、計算窗口內(nèi)統(tǒng)計量)應(yīng)該能在常數(shù)時間內(nèi)完成,以保證算法的高效性。
滑動窗口算法因其靈活性和高效性,在眾多領(lǐng)域中都有重要應(yīng)用,是理解和處理時間序列數(shù)據(jù)的一個非常實用的工具。
要實現(xiàn)AOP結(jié)合滑動窗口算法來實現(xiàn)自定義規(guī)則的限流,我們可以在原有的基礎(chǔ)上進(jìn)一步擴(kuò)展,以支持更靈活的配置和更復(fù)雜的規(guī)則。以下是一個基于Spring AOP和滑動窗口算法的簡單示例,包括自定義注解來設(shè)置限流規(guī)則,以及如何在切面中應(yīng)用這些規(guī)則。
定義緩存注解
首先,定義一個自定義注解來標(biāo)記需要限流的方法,并允許傳入限流的具體規(guī)則
package com.example.demo.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface WindowRateLimit {
// 允許的最大請求數(shù)
int limit();
// 窗口時間長度,單位毫秒
long timeWindowMilliseconds();
}滑動窗口限流器
接下來,實現(xiàn)滑動窗口限流器,這里簡化處理,直接使用內(nèi)存實現(xiàn),實際應(yīng)用中可能需要基于Redis等持久化存儲以適應(yīng)分布式場景:
核心思想:每次請求進(jìn)來時,獲取當(dāng)前時間的時間戳,將每次請求的時間戳存儲到LinkedList集合中,同時以當(dāng)前時間為窗口期的結(jié)束點,刪除往前一個窗口期內(nèi)所有的請求時間戳,將LinkedList集合剩余數(shù)據(jù)的個數(shù)與自定義設(shè)置的窗口期請求峰值進(jìn)行對比,若等于則直接限流。
package com.example.demo.uitls;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.LinkedList;
/**
* SlidingWindowRateLimiter : 滑動窗口限流算法
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SlidingWindowRateLimiter implements Serializable {
/**
* 請求隊列
*/
private LinkedList<Long> requests = new LinkedList<>();
/**
* 最大請求數(shù)
*/
private int maxRequests;
/**
* 窗口大小
*/
private long windowSizeInMilliseconds;
public SlidingWindowRateLimiter(int maxRequests, long windowSizeInMilliseconds) {
this.maxRequests = maxRequests;
this.windowSizeInMilliseconds = windowSizeInMilliseconds;
}
/**
* 判斷是否允許請求
* @return
*/
public synchronized boolean allowRequest() {
// 獲取當(dāng)前時間
long currentTime = System.currentTimeMillis();
// 清除窗口之外的舊請求
while (!requests.isEmpty() && currentTime - requests.peekFirst() > windowSizeInMilliseconds) {
requests.removeFirst();
}
// 如果當(dāng)前窗口請求未達(dá)到上限,則允許請求并記錄
if (requests.size() < maxRequests) {
requests.addLast(currentTime);
return true;
} else {
// 達(dá)到限流閾值,拒絕請求
return false;
}
}
}AOP切面實現(xiàn)
最后,創(chuàng)建AOP切面來應(yīng)用限流邏輯:
將需要限流的方法所初始化的滑動窗口限流器緩存到Redis中,過期時間設(shè)置為對應(yīng)的窗口時間。
一個窗口時間內(nèi),若沒有新的請求進(jìn)來,即存儲的請求時間戳都為窗口期外的,因此可以直接清除掉已減少緩存占用空間。
package com.example.demo.aspect;
import com.example.demo.annotation.WindowRateLimit;
import com.example.demo.config.redis.RedisKeyEnum;
import com.example.demo.uitls.RedisUtil;
import com.example.demo.uitls.SlidingWindowRateLimiter;
import jakarta.annotation.Resource;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* RateLimiterAspect :
*/
@Aspect
@Component
public class SlidingWindowRateLimiterAspect {
@Resource
private RedisUtil redisUtil;
@Around("@annotation(rateLimit)")
public Object applyRateLimit(ProceedingJoinPoint joinPoint, WindowRateLimit rateLimit) throws Throwable {
// 獲取調(diào)用的方法名
String methodName = joinPoint.getSignature().getName();
// 獲取方法對應(yīng)的緩存滑動窗口限流器KEY
String key = RedisKeyEnum.WINDOW_CURRENT_LIMITING.getKey() + methodName;
// 從緩存中獲取滑動窗口限流器
SlidingWindowRateLimiter rateLimiter = redisUtil.getCacheObject(key);
// 如果滑動窗口限流器不存在,則創(chuàng)建一個新限流器
if (rateLimiter == null) {
rateLimiter = new SlidingWindowRateLimiter(rateLimit.limit(), rateLimit.timeWindowMilliseconds());
}
// 如果滑動窗口限流器存在,則判斷是否允許請求
if (!rateLimiter.allowRequest()) {
throw new RuntimeException("Too many requests, please try again later.");
}
// 如果允許請求,則更新滑動窗口限流器,緩存過期時間設(shè)置為滑動窗口限流器時間窗口
redisUtil.setCacheObject(key, rateLimiter, rateLimit.timeWindowMilliseconds(), TimeUnit.MILLISECONDS);
// 允許執(zhí)行方法
return joinPoint.proceed();
}
}應(yīng)用限流注解
在需要做限流的方法上加上注解,在注解參數(shù)中設(shè)定 允許的最大請求數(shù) 和 窗口時間長度(單位毫秒)
package com.example.demo.service.impl;
import com.example.demo.annotation.WindowRateLimit;
import com.example.demo.service.TestService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@Service
public class TestServiceImpl implements TestService {
@Override
@WindowRateLimit(limit = 5, timeWindowMilliseconds = 60L*1000) // 每最多允許5次請求
public String getContent() {
return "Hello Word";
}
}首次請求時,初始化滑動窗口限流器,記錄第一次請求的時間戳

窗口期內(nèi),記錄了五次請求的時間戳后,已達(dá)到我們在注解中設(shè)置的窗口期最大請求量

此時接口限流

到此這篇關(guān)于Java AOP實現(xiàn)自定義滑動窗口限流器方法詳解的文章就介紹到這了,更多相關(guān)Java滑動窗口限流器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring?Boot使用線程池處理上萬條數(shù)據(jù)插入功能
這篇文章主要介紹了Spring?Boot使用線程池處理上萬條數(shù)據(jù)插入功能,使用步驟是先創(chuàng)建一個線程池的配置,讓Spring Boot加載,用來定義如何創(chuàng)建一個ThreadPoolTaskExecutor,本文通過實例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2022-08-08
javaweb判斷當(dāng)前請求是否為移動設(shè)備訪問的方法
這篇文章主要為大家詳細(xì)介紹了javaweb判斷當(dāng)前請求是否為移動設(shè)備訪問的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-05-05
Spring MVC整合Shiro權(quán)限控制的方法
這篇文章主要介紹了Spring MVC整合Shiro權(quán)限控制,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-05-05
SpringBoot集成SFTP客戶端實現(xiàn)文件上傳下載實例
這篇文章主要為大家介紹了SpringBoot集成SFTP客戶端實現(xiàn)文件上傳下載實例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
spring boot使用自定義的線程池執(zhí)行Async任務(wù)
這篇文章主要介紹了spring boot使用自定義的線程池執(zhí)行Async任務(wù)的相關(guān)資料,需要的朋友可以參考下2018-02-02
springboot項目如何在linux服務(wù)器上啟動、停止腳本
這篇文章主要介紹了springboot項目如何在linux服務(wù)器上啟動、停止腳本問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-05-05
SpringBoot整合MyBatis實現(xiàn)樂觀鎖和悲觀鎖的示例
這篇文章主要介紹了SpringBoot整合MyBatis實現(xiàn)樂觀鎖和悲觀鎖的示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09

