-
限流:系统能力有限或出现有恶意请求时,需要组织部分请求,防止系统压力过大造成宕机。也就是在规定时间内一个操作只能执行有限次数,超出就是非法行为。
- 简单限流:通过维护某个时间区间,判断改时间区间内发生的次数。
- 漏斗限流:在每次试图处理请求前,先计算和上一次请求的间隔,并恢复该部分的空间。只有在空间允许的情况下才会放行该请求。
- 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