限流算法之滑動窗口法


import java.util.Date;
import java.util.Deque;
import java.util.concurrent.LinkedBlockingDeque;

/**
* 滑動窗口限流
* 使用方式:
* 1.使用時創建一個SlideCounterLimiter對象
* 2.請求進入調用requestAccess()方法,根據訪問結果判斷是否被限制訪問
*
* 設計思路:
* 1.滑動窗口被分割成N個子窗口,創建一個限制長度為N的隊列
* 2.隊列首部是最老的子窗口,隊列尾部是最新的子窗口
* 3.每個請求都需要找到自己所在的子窗口,如果沒有就創建,並加入到隊列,如果隊列已滿
* 就把隊首最老的子窗口踢除,踢出后統計的請求數要減去被踢出的子窗口接收的請求數
* 4.所有子窗口是時間上連續的
* 5.對所有的請求進行區分,第一次請求和后續任意一次請求
* 6.第一次請求,需要首先初始化(創建一個空的隊列,請求時間作為開始時間創建第一個子窗口,被訪問總次數設置0次),第
* 次請求必然在第一個子窗口時間內,子窗口的受訪次數+1,被訪問總次數+1。
* 7.后續任意一次請求,可分為三種情況
* 8.第一種情況,請求進入直接被統計入隊尾的子窗口
* 9.第二種情況,請求進入后需要以隊尾子窗口不斷創建后續子窗口並加入隊列,直到請求時間可以被統計入新建的子窗口內
* 10.第三種情況,是對第二種情況的優化,當請求訪問離上一次請求間隔太過久遠,可以直接初始化作為一個新的開始,不必
* 用步驟2的處理方式,以減少性能消耗
* 11.對於上述第二種情況,因為是請求驅動的滑動效果,請求時間是不可預估的,對於一定時間間隔內的請求,可能需要向隊
* 列補齊斷層的子窗口,建議不要使用過大的分割數
*
*/
public class SlideCounterLimiter {
/**
* 限流窗口時間長度
*/
private long periodTime;
/**
* 限流窗口訪問量上限
*/
private long limitTimes;
/**
* 限流窗口分割成子窗口數
*/
private int partSize;
/**
* 子窗口時間長度
*/
private long subPeriodTime;
/**
* 最新子窗口起始時間(用於判斷是否生產子窗口及計算子窗口起始結束時間)
*/
private long currentTimeStart;
/**
* 最新子窗口結束時間(用於判斷是否生產子窗口及計算子窗口起始結束時間)
*/
private long currentTimeEnd;
/**
* 雙向隊列
* 1.存放子窗口
* 2.長度為子窗口數
* 3.最新子窗口尾部添加,最老子窗口頭部刪除,實現滑動
*/
private Deque<SubCounterLimiter> deque;
/**
* 滑動窗口總訪問數
* 1.記錄滑動窗口目前訪問總量
* 2.窗口滑動時,減去最老子窗口的訪問量
* 3.訪問成功 + 1
*/
private long acceptTimes;

public SlideCounterLimiter(long periodTime, long limitTimes, int partSize) {
this.periodTime = periodTime;
this.limitTimes = limitTimes;
this.partSize = partSize;
this.subPeriodTime = periodTime/partSize;
}

/**
* 子窗口類
*/
private class SubCounterLimiter{
/**
* 子窗口起始時間
*/
private long subTimeStart;
/**
* 子窗口結束時間
*/
private long subTimeEnd;
/**
* 子窗口接受訪問數
*/
private long subAcceptTimes;

public SubCounterLimiter(long subTimeStart, long subTimeEnd) {
this.subTimeStart = subTimeStart;
this.subTimeEnd = subTimeEnd;
this.subAcceptTimes = 0L;
}

public long getSubTimeStart() {
return subTimeStart;
}

public long getSubTimeEnd() {
return subTimeEnd;
}

public long getSubAcceptTimes() {
return subAcceptTimes;
}

public void setSubTimeStart(long subTimeStart) {
this.subTimeStart = subTimeStart;
}

public void setSubTimeEnd(long subTimeEnd) {
this.subTimeEnd = subTimeEnd;
}

public void setSubAcceptTimes(long subAcceptTimes) {
this.subAcceptTimes = subAcceptTimes;
}
}

/**
* 步驟1.區分第一次請求和后續請求,第一次請求執行初始化方法
* 步驟2.后續請求,分三種情況:
* <1>.請求時間與最新一次子窗口的結束時間相差超過一個periodTime,執行初始化方法
* 作為一次新的開始
* <2>.請求時間與最新一次子窗口的結束時間相差少於一個periodTime且不在最新子窗口
* 的時間段內,則創建新的子窗口且加入隊列,並且隊列已滿時擠出最老的子窗口和a-
* -cceptTimes減去擠出子窗口的訪問量,直到請求時間在最新的子窗口時間段內
* <3>.請求時間在最新一次子窗口內
* 步驟3.步驟1和步驟2保證了當前請求在最新子窗口時間段內
* 步驟4.訪問次數進行判斷,是否可以訪問,成功訪問則總訪問量+1,子窗口訪問量+1
*/
public synchronized boolean requestAccess(){

long requestTime = new Date().getTime();//請求時間

if(deque==null){//第一次請求
init(requestTime);
}else{//后續任意一次請求
if(requestTime>=periodTime+deque.getLast().getSubTimeEnd()){
init(requestTime);
}else if(requestTime>=deque.getLast().getSubTimeEnd() && requestTime<periodTime+deque.getLast().getSubTimeEnd()){
recur(requestTime);
}
}

System.out.println("限流時間段:["+deque.getFirst().getSubTimeStart()+"]至["+deque.getLast().getSubTimeEnd() +"]");
System.out.println("訪問時間:"+requestTime);
System.out.println("目前訪問量:"+acceptTimes);

if(acceptTimes<limitTimes){
acceptTimes++;
deque.getLast().setSubAcceptTimes(deque.getLast().getSubAcceptTimes()+1);
System.out.println("訪問結果:成功!");
return true;
}else{
System.out.println("訪問結果:失敗,系統忙,稍后再試!");
return false;
}

}

/**
* 1.訪問量初始化
* 2.隊列初始化
* 3.創建第一個子窗口
* 4.隊列加入子窗口
*/
private void init(long requestTime){
System.out.println("初始化...");
acceptTimes = 0L;
deque = new LinkedBlockingDeque<SubCounterLimiter>(partSize);
currentTimeStart = requestTime;
currentTimeEnd = currentTimeStart + subPeriodTime;
SubCounterLimiter initFirst = createSubCounterLimiter(currentTimeStart,currentTimeEnd);
deque.add(initFirst);
}

/**
* 1.遞歸獲得子窗口,並更新入隊列
* 2.直到請求時刻在獲得子窗口時間區間內,該子窗口作為最新子窗口,遞歸創建子窗口停止
*/
private void recur(long requestTime){
currentTimeStart = deque.getLast().getSubTimeEnd();
currentTimeEnd = currentTimeStart + subPeriodTime;
SubCounterLimiter last = createSubCounterLimiter(currentTimeStart,currentTimeEnd);
while(!deque.offerLast(last)){
acceptTimes = acceptTimes - deque.getFirst().getSubAcceptTimes();
deque.removeFirst();
}
if(requestTime>=deque.getLast().getSubTimeEnd()){
recur(requestTime);
}
}

/**
* 創建子窗口
*/
private SubCounterLimiter createSubCounterLimiter(long timeStart,long timeEnd) {
return new SubCounterLimiter(timeStart, timeEnd);
}

}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM