文章目录
限流实现一ParamFlowSlot
ParamFlowChecker.passCheck
- 方法签名的参数个数小于配置的热点限流位置不进行限流
- 获取限流参数值
- 如果入参是一个ParamFlowArgument类型 修改参数值为paramFlowKey()返回值
- 无值填充则不进行限流
- 集群qps限流
- 单机限流
public final class ParamFlowChecker {
public static boolean passCheck(ResourceWrapper resourceWrapper, /*@Valid*/ ParamFlowRule rule, /*@Valid*/ int count,
Object... args) {
方法签名的参数个数小于配置的热点限流位置不进行限流
int paramIdx = rule.getParamIdx();
if (args.length <= paramIdx) {
return true;
}
获取限流的值 【sentinel目前只支持对基本参数类型外加String进行限流】
Object value = args[paramIdx];
如果入参是一个ParamFlowArgument类型 则根据其的paramFlowKey获取的值进行限流
使用场景:入参是某个复合对象 比如一个用户[user_id,user_name] user_name有重复,而我们只想使用user_id限流,此时使User 实现ParamFlowArgument,覆写paramFlowKey方法
if (value instanceof ParamFlowArgument) {
value = ((ParamFlowArgument) value).paramFlowKey();
}
无值填充则不进行限流
if (value == null) {
return true;
}
集群检测 qps限流
if (rule.isClusterMode() && rule.getGrade() == RuleConstant.FLOW_GRADE_QPS) {
return passClusterCheck(resourceWrapper, rule, count, value);
}
单机检测
return passLocalCheck(resourceWrapper, rule, count, value);
}
}
集群模式一passClusterCheck
- 获取TokenService,作为嵌入式节点,其可能本身就是server,也可能是client
- 如果没有配置集群的server和client节点,则降级
- client远程调用server获取限流结果
- TokenService如果是server直接调用内部算法进行限流判断
private static boolean passClusterCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count,
try {
Collection<Object> params = toCollection(value);
获取server或者client实例 如果未配置则降级成直接通过或者单机限流模式
TokenService clusterService = pickClusterService();
if (clusterService == null) {
管控台没有配置集群模式,或者配置的集群不包含当前节点信息
return fallbackToLocalOrPass(resourceWrapper, rule, count, params);
}
TokenResult result = clusterService.requestParamToken(rule.getClusterConfig().getFlowId(), count, params);
switch (result.getStatus()) {
case TokenResultStatus.OK:
return true;
case TokenResultStatus.BLOCKED:
return false;
default:
根据配置直接通过限流或者退化到 local 模式的限流
return fallbackToLocalOrPass(resourceWrapper, rule, count, params);
}
} catch (Throwable ex) {
若用户未引入集群限流 client 相关依赖,或者 client未开启/连接失败/通信失败
根据配置直接通过限流或者退化到 local 模式的限流
return fallbackToLocalOrPass(resourceWrapper, rule, count, value);
}
}
获取限流信息一requestParamToken
如果节点是server节点,执行如下,如果是client节点则通过通信调用以下代码
- 通过tokenserver调用ClusterFlowChecker进行限流
public class DefaultEmbeddedTokenServer implements EmbeddedClusterTokenServer {
public TokenResult requestParamToken(Long ruleId, int acquireCount, Collection<Object> params) {
if (tokenService != null) {
调用DefaultTokenService进行限流
return tokenService.requestParamToken(ruleId, acquireCount, params);
}
return new TokenResult(TokenResultStatus.FAIL);
}
}
public TokenResult requestParamToken(Long ruleId, int acquireCount, Collection<Object> params) {
if (notValidRequest(ruleId, acquireCount) || params == null || params.isEmpty()) {
return badRequest();
}
ParamFlowRule rule = ClusterParamFlowRuleManager.getParamRuleById(ruleId);
if (rule == null) {
return new TokenResult(TokenResultStatus.NO_RULE_EXISTS);
}
调用ClusterFlowChecker进行窗口限流
return ClusterParamFlowChecker.acquireClusterToken(rule, acquireCount, params);
}
限流实现一ClusterFlowChecker
public final class ClusterParamFlowChecker {
static TokenResult acquireClusterToken(ParamFlowRule rule, int count, Collection<Object> values) {
Long id = rule.getClusterConfig().getFlowId();
// 保护机制
if (!allowProceed(id)) {
return new TokenResult(TokenResultStatus.TOO_MANY_REQUEST);
}
// 获取度量信息
ClusterParamMetric metric = ClusterParamMetricStatistics.getMetric(id);
if (metric == null) {
不存在直接返回fail
return new TokenResult(TokenResultStatus.FAIL);
}
if (values == null || values.isEmpty()) {
无参直接通过限流
return new TokenResult(TokenResultStatus.OK);
}
double remaining = -1;
boolean hasPassed = true;
Object blockObject = null;
只要有一个值没有通过限流检查则限流
for (Object value : values) {
double latestQps = metric.getAvg(value);
没有配置参数级别则使用总的阈值
double threshold = calcGlobalThreshold(rule, value);
double nextRemaining = threshold - latestQps - count;
remaining = nextRemaining;
if (nextRemaining < 0) {
hasPassed = false;
blockObject = value;
break;
}
}
通过限流增加统计数据
if (hasPassed) {
for (Object value : values) {
metric.addValue(value, count);
}
ClusterServerStatLogUtil.log(String.format("param|pass|%d", id));
} else {
ClusterServerStatLogUtil.log(String.format("param|block|%d|%s", id, blockObject));
}
if (values.size() > 1) {
// Remaining field is unsupported for multi-values.
remaining = -1;
}
返回限流结果
return hasPassed ? newPassResponse((int)remaining): newBlockResponse();
}
}
单机模式一passLocalCheck
- 判断qps还是thread数限流
- qps限流: 流控效果为排队等待执行passThrottleLocalCheck
- qps限流: 流控效果不为排队等待执行passDefaultLocalCheck
passDefaultLocalCheck引入令牌桶,为令牌桶实现,这里不再叙述
private static boolean passLocalCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count,
......删除其他代码
单机检测
if (!passSingleValueCheck(resourceWrapper, rule, count, param)) {
return false;
}
return true;
}
static boolean passSingleValueCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int acquireCount,
Object value) {
如果是qps限流
if (rule.getGrade() == RuleConstant.FLOW_GRADE_QPS) {
流控效果: 排队等待
if (rule.getControlBehavior() == RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER) {
如果没有获取则在统计窗口时间范围内排队获取
return passThrottleLocalCheck(resourceWrapper, rule, acquireCount, value);
} else {
流控效果:
return passDefaultLocalCheck(resourceWrapper, rule, acquireCount, value);
}
如果是线程数限流
} else if (rule.getGrade() == RuleConstant.FLOW_GRADE_THREAD) {
Set<Object> exclusionItems = rule.getParsedHotItems().keySet();
获取线程数
long threadCount = getParameterMetric(resourceWrapper).getThreadCount(rule.getParamIdx(), value);
热点参数线程数比较
if (exclusionItems.contains(value)) {
int itemThreshold = rule.getParsedHotItems().get(value);
return ++threadCount <= itemThreshold;
}
普通参数线程数比较
long threshold = (long)rule.getCount();
return ++threadCount <= threshold;
}
return true;
}
passThrottleLocalCheck
- 获取度量数据[限流统计数据]
- 如果当前参数值匹配了指定值 则根据指定值配置的限流信息进行限流
- 在一个配置窗口时间段内可以不断等待超过窗口则返回false
- 如果需要等待则执行线程休眠waitTime
static boolean passThrottleLocalCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int acquireCount,
Object value) {
获取度量数据[限流统计数据]
ParameterMetric metric = getParameterMetric(resourceWrapper);
获取 规则对应的限流数据
Object为方法入参参数值, AtomicLong为参数值对应的限流qps
CacheMap<Object, AtomicLong> timeRecorderMap = metric == null ? null : metric.getRuleTimeCounter(rule);
if (timeRecorderMap == null) {
return true;
}
// Calculate max token count (threshold)
Set<Object> exclusionItems = rule.getParsedHotItems().keySet();
long tokenCount = (long)rule.getCount();
如果当前参数值匹配了指定值 则根据指定值配置的限流信息进行限流
if (exclusionItems.contains(value)) {
tokenCount = rule.getParsedHotItems().get(value);
}
if (tokenCount == 0) {
return false;
}
获取的数量 * 统计窗口的时长/ 规则配置阈值 * 1000[秒转毫秒]
在一个配置窗口时间段内可以不断等待超过窗口则返回false
long costTime = Math.round(1.0 * 1000 * acquireCount * rule.getDurationInSec() / tokenCount);
while (true) {
long currentTime = TimeUtil.currentTimeMillis();
AtomicLong timeRecorder = timeRecorderMap.putIfAbsent(value, new AtomicLong(currentTime));
if (timeRecorder == null) {
return true;
}
//AtomicLong timeRecorder = timeRecorderMap.get(value);
long lastPassTime = timeRecorder.get();
排队上限时间
long expectedTime = lastPassTime + costTime;
if (expectedTime <= currentTime || expectedTime - currentTime < rule.getMaxQueueingTimeMs()) {
更新排队时间上限
AtomicLong lastPastTimeRef = timeRecorderMap.get(value);
if (lastPastTimeRef.compareAndSet(lastPassTime, currentTime)) {
最多还可以等多久
long waitTime = expectedTime - currentTime;
if (waitTime > 0) {
lastPastTimeRef.set(expectedTime);
try {
直接让出cpu
TimeUnit.MILLISECONDS.sleep(waitTime);
} catch (InterruptedException e) {
RecordLog.warn("passThrottleLocalCheck: wait interrupted", e);
}
}
return true;
} else {
cas 失败重新计算
Thread.yield();
}
} else {
超过排队时间直接报错
return false;
}
}
}