-
限流:系統能力有限或出現有惡意請求時,需要組織部分請求,防止系統壓力過大造成宕機。也就是在規定時間內一個操作只能執行有限次數,超出就是非法行爲。
- 簡單限流:通過維護某個時間區間,判斷改時間區間內發生的次數。
- 漏斗限流:在每次試圖處理請求前,先計算和上一次請求的間隔,並恢復該部分的空間。只有在空間允許的情況下才會放行該請求。
- redis不能直接使用以下代碼思路,因爲無法保證操作的原子性,如果爲了原子性加鎖又會降低性能。Redis中提供了Redis-Cell,該模塊使用了漏斗算法,並提供了原子的限流指令。使用非常簡單,指令爲:
返回值包括請求結果,漏斗容量,剩餘容量,重試時間,以及漏斗完全空出來的剩餘時間。cl.throttle userId:actionId capacity operation time needCapacity
-
簡單代碼實現
package com.xliu.chapter1;
import java.sql.Time;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @author liuxin
* @version 1.0
* @date 2020/4/22 18:44
*/
public class FunnelRateLimeter {
static class Funnel {
int capatity;
float leakingRate;
int leftQuota;
long leakingTs;
public Funnel(int capatity, float leakingRate) {
this.capatity = capatity;
this.leakingRate = leakingRate;
this.leftQuota = capatity;
this.leakingTs = System.currentTimeMillis();
}
void makeSpace() {
long nowTs = System.currentTimeMillis();
long deltaTs = nowTs - leakingTs;
int deltaQuota = (int) (deltaTs * leakingRate);
//間隔時間很久,溢出了
if (deltaQuota < 0) {
this.leftQuota = capatity;
this.leakingTs = nowTs;
return;
}
if (deltaQuota < 1) {
return;
}
this.leftQuota += deltaQuota;
this.leakingTs = nowTs;
if (this.leftQuota > this.capatity) {
this.leftQuota = this.capatity;
}
}
boolean watering(int quota) {
makeSpace();
if (this.leftQuota >= quota) {
this.leftQuota -= quota;
return true;
}
return false;
}
}
private Map<String, Funnel> funnels = new HashMap<>();
public boolean isActionAllowed(String userId, String actionKey, int capacity, float leakingRate) {
String key = String.format("%s:%s", userId, actionKey);
Funnel funnel = funnels.get(key);
if (funnel == null) {
funnel = new Funnel(capacity, leakingRate);
funnels.put(key, funnel);
}
return funnel.watering(1);
}
public static void main(String[] args) {
FunnelRateLimeter funnelRateLimeter = new FunnelRateLimeter();
String userId = "liuxin";
String actionId = "do";
float leakingRate = 5.0f / 1000;
int capacity = 5;
for (int i = 0; i < 22; i++) {
boolean result = funnelRateLimeter.isActionAllowed(userId, actionId, capacity, leakingRate);
System.out.println("第" + i + "次 :" + result);
if (!result) {
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
- 實驗結果,參數限定爲每一秒只能執行5次,運行結果可以看到第六次請求被拒絕了,等到200ms,恢復了1空間後,第7次請求成功和預期結果一致。
第0次 :true
第1次 :true
第2次 :true
第3次 :true
第4次 :true
第5次 :false
第6次 :false
第7次 :true
第8次 :false
第9次 :false
第10次 :true
第11次 :false
第12次 :false
第13次 :true
第14次 :false
第15次 :false
第16次 :true
第17次 :false
第18次 :false
第19次 :true
第20次 :false
第21次 :false