import com.alibaba.fastjson.JSONObject;import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author haosenwei[haosenwei@dubmic.com]
* @date 2019-06-14 14:59
* <p>Copyright 2008-2019 snsndk.com</p>
*/
@Component
public class FunnelRateLimiter {
@Autowired
ICache iCache;
/**
* 漏斗容量
*/
private static final int CAPACITY = 3;
/**
* 每单个单位时间允许的流量
*/
private static final int ALLOWQUOTA = 3;
/**
* 单位时间(秒)
*/
private static final int PERSECOND = 1;
/**
* 判断是否可以访问
*
* @param did 唯一标识
* @param action 操作
* @return 是否运行访问
*/
public boolean isActionAllowed(String did, String action) {
return this.isActionAllowed(did, action, CAPACITY, ALLOWQUOTA, PERSECOND);
}
/**
* 根据给定的漏斗参数检查是否允许访问
*
* @param did 用户名
* @param action 操作
* @param capacity 漏斗容量
* @param allowQuota 每单个单位时间允许的流量
* @param perSecond 单位时间(秒)
* @return 是否允许访问
*/
public boolean isActionAllowed(String did, String action, int capacity, int allowQuota, int perSecond) {
String key = "funnel:" + action + ":" + did;
String s = iCache.get(key);
Funnel funnel = null;
if (StringUtils.isBlank(s)) {
funnel = new Funnel(capacity, allowQuota, perSecond);
this.iCache.set(key, JSONObject.toJSONString(funnel));
} else {
funnel = JSONObject.parseObject(s, Funnel.class);
}
return funnel.watering(1);
}
private static class Funnel {
private int capacity;
private float leakingRate;
private int leftQuota;
private long leakingTs;
public Funnel(int capacity, int count, int perSecond) {
this.capacity = capacity;
// 因为计算使用毫秒为单位的
perSecond *= 1000;
this.leakingRate = (float) count / perSecond;
}
/**
* 根据上次水流动的时间,腾出已流出的空间
*/
private void makeSpace() {
long now = System.currentTimeMillis();
long time = now - leakingTs;
int leaked = (int) (time * leakingRate);
if (leaked < 1) {
return;
}
leftQuota += leaked;
// 如果剩余大于容量,则剩余等于容量
if (leftQuota > capacity) {
leftQuota = capacity;
}
leakingTs = now;
}
/**
* 漏斗漏水
*
* @param quota 流量
* @return 是否有足够的水可以流出(是否允许访问)
*/
public boolean watering(int quota) {
makeSpace();
int left = leftQuota - quota;
if (left >= 0) {
leftQuota = left;
return true;
}
return false;
}
}
}