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; } } }