- Extract flow rule checker from legacy `passCheck` - Remove redundant code - Refactor FlowSlot (some logic moved from FlowRuleManager) - Rename `Controller` to `TrafficShapingController` Signed-off-by: Eric Zhao <sczyh16@gmail.com>master
@@ -15,18 +15,15 @@ | |||||
*/ | */ | ||||
package com.alibaba.csp.sentinel.slots.block.flow; | package com.alibaba.csp.sentinel.slots.block.flow; | ||||
import com.alibaba.csp.sentinel.util.StringUtil; | |||||
import com.alibaba.csp.sentinel.context.Context; | import com.alibaba.csp.sentinel.context.Context; | ||||
import com.alibaba.csp.sentinel.node.DefaultNode; | import com.alibaba.csp.sentinel.node.DefaultNode; | ||||
import com.alibaba.csp.sentinel.node.Node; | |||||
import com.alibaba.csp.sentinel.slots.block.AbstractRule; | import com.alibaba.csp.sentinel.slots.block.AbstractRule; | ||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant; | import com.alibaba.csp.sentinel.slots.block.RuleConstant; | ||||
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; | |||||
/*** | |||||
/** | |||||
* <p> | * <p> | ||||
* Each flow rule is mainly composed of three factors: <strong>grade</strong>, | |||||
* <strong>strategy</strong> and <strong>controlBehavior</strong>. | |||||
* Each flow rule is mainly composed of three factors: <strong>grade</strong>, | |||||
* <strong>strategy</strong> and <strong>controlBehavior</strong>: | |||||
* </p> | * </p> | ||||
* <ul> | * <ul> | ||||
* <li>The {@link #grade} represents the threshold type of flow control (by QPS or thread count).</li> | * <li>The {@link #grade} represents the threshold type of flow control (by QPS or thread count).</li> | ||||
@@ -45,6 +42,12 @@ public class FlowRule extends AbstractRule { | |||||
setLimitApp(RuleConstant.LIMIT_APP_DEFAULT); | setLimitApp(RuleConstant.LIMIT_APP_DEFAULT); | ||||
} | } | ||||
public FlowRule(String resourceName) { | |||||
super(); | |||||
setResource(resourceName); | |||||
setLimitApp(RuleConstant.LIMIT_APP_DEFAULT); | |||||
} | |||||
/** | /** | ||||
* The threshold type of flow control (0: thread count, 1: QPS). | * The threshold type of flow control (0: thread count, 1: QPS). | ||||
*/ | */ | ||||
@@ -65,7 +68,7 @@ public class FlowRule extends AbstractRule { | |||||
private int strategy = RuleConstant.STRATEGY_DIRECT; | private int strategy = RuleConstant.STRATEGY_DIRECT; | ||||
/** | /** | ||||
* Reference resource in flow control with relevant resource. | |||||
* Reference resource in flow control with relevant resource or context. | |||||
*/ | */ | ||||
private String refResource; | private String refResource; | ||||
@@ -82,7 +85,10 @@ public class FlowRule extends AbstractRule { | |||||
*/ | */ | ||||
private int maxQueueingTimeMs = 500; | private int maxQueueingTimeMs = 500; | ||||
private Controller controller; | |||||
/** | |||||
* The traffic shaping (throttling) controller. | |||||
*/ | |||||
private TrafficShapingController controller; | |||||
public int getControlBehavior() { | public int getControlBehavior() { | ||||
return controlBehavior; | return controlBehavior; | ||||
@@ -102,11 +108,15 @@ public class FlowRule extends AbstractRule { | |||||
return this; | return this; | ||||
} | } | ||||
public FlowRule setRater(Controller rater) { | |||||
FlowRule setRater(TrafficShapingController rater) { | |||||
this.controller = rater; | this.controller = rater; | ||||
return this; | return this; | ||||
} | } | ||||
TrafficShapingController getRater() { | |||||
return controller; | |||||
} | |||||
public int getWarmUpPeriodSec() { | public int getWarmUpPeriodSec() { | ||||
return warmUpPeriodSec; | return warmUpPeriodSec; | ||||
} | } | ||||
@@ -154,90 +164,7 @@ public class FlowRule extends AbstractRule { | |||||
@Override | @Override | ||||
public boolean passCheck(Context context, DefaultNode node, int acquireCount, Object... args) { | public boolean passCheck(Context context, DefaultNode node, int acquireCount, Object... args) { | ||||
String limitApp = this.getLimitApp(); | |||||
if (limitApp == null) { | |||||
return true; | |||||
} | |||||
String origin = context.getOrigin(); | |||||
Node selectedNode = selectNodeByRequesterAndStrategy(origin, context, node); | |||||
if (selectedNode == null) { | |||||
return true; | |||||
} | |||||
return controller.canPass(selectedNode, acquireCount); | |||||
} | |||||
private Node selectNodeByRequesterAndStrategy(String origin, Context context, DefaultNode node) { | |||||
// The limit app should not be empty. | |||||
String limitApp = this.getLimitApp(); | |||||
if (limitApp.equals(origin)) { | |||||
if (strategy == RuleConstant.STRATEGY_DIRECT) { | |||||
return context.getOriginNode(); | |||||
} | |||||
String refResource = this.getRefResource(); | |||||
if (StringUtil.isEmpty(refResource)) { | |||||
return null; | |||||
} | |||||
if (strategy == RuleConstant.STRATEGY_RELATE) { | |||||
return ClusterBuilderSlot.getClusterNode(refResource); | |||||
} | |||||
if (strategy == RuleConstant.STRATEGY_CHAIN) { | |||||
if (!refResource.equals(context.getName())) { | |||||
return null; | |||||
} | |||||
return node; | |||||
} | |||||
} else if (RuleConstant.LIMIT_APP_DEFAULT.equals(limitApp)) { | |||||
if (strategy == RuleConstant.STRATEGY_DIRECT) { | |||||
return node.getClusterNode(); | |||||
} | |||||
String refResource = this.getRefResource(); | |||||
if (StringUtil.isEmpty(refResource)) { | |||||
return null; | |||||
} | |||||
if (strategy == RuleConstant.STRATEGY_RELATE) { | |||||
return ClusterBuilderSlot.getClusterNode(refResource); | |||||
} | |||||
if (strategy == RuleConstant.STRATEGY_CHAIN) { | |||||
if (!refResource.equals(context.getName())) { | |||||
return null; | |||||
} | |||||
return node; | |||||
} | |||||
} else if (RuleConstant.LIMIT_APP_OTHER.equals(limitApp) | |||||
&& FlowRuleManager.isOtherOrigin(origin, getResource())) { | |||||
if (strategy == RuleConstant.STRATEGY_DIRECT) { | |||||
return context.getOriginNode(); | |||||
} | |||||
String refResource = this.getRefResource(); | |||||
if (StringUtil.isEmpty(refResource)) { | |||||
return null; | |||||
} | |||||
if (strategy == RuleConstant.STRATEGY_RELATE) { | |||||
return ClusterBuilderSlot.getClusterNode(refResource); | |||||
} | |||||
if (strategy == RuleConstant.STRATEGY_CHAIN) { | |||||
if (!refResource.equals(context.getName())) { | |||||
return null; | |||||
} | |||||
if (node != null) { | |||||
return node; | |||||
} | |||||
} | |||||
} | |||||
return null; | |||||
return true; | |||||
} | } | ||||
@Override | @Override | ||||
@@ -0,0 +1,106 @@ | |||||
/* | |||||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||||
* | |||||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||||
* you may not use this file except in compliance with the License. | |||||
* You may obtain a copy of the License at | |||||
* | |||||
* http://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* Unless required by applicable law or agreed to in writing, software | |||||
* distributed under the License is distributed on an "AS IS" BASIS, | |||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
* See the License for the specific language governing permissions and | |||||
* limitations under the License. | |||||
*/ | |||||
package com.alibaba.csp.sentinel.slots.block.flow; | |||||
import com.alibaba.csp.sentinel.context.Context; | |||||
import com.alibaba.csp.sentinel.node.DefaultNode; | |||||
import com.alibaba.csp.sentinel.node.Node; | |||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant; | |||||
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; | |||||
import com.alibaba.csp.sentinel.util.StringUtil; | |||||
/** | |||||
* Rule checker for flow control rules. | |||||
* | |||||
* @author Eric Zhao | |||||
*/ | |||||
final class FlowRuleChecker { | |||||
static boolean passCheck(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node, int acquireCount) { | |||||
String limitApp = rule.getLimitApp(); | |||||
if (limitApp == null) { | |||||
return true; | |||||
} | |||||
Node selectedNode = selectNodeByRequesterAndStrategy(rule, context, node); | |||||
if (selectedNode == null) { | |||||
return true; | |||||
} | |||||
return rule.getRater().canPass(selectedNode, acquireCount); | |||||
} | |||||
static Node selectReferenceNode(FlowRule rule, Context context, DefaultNode node) { | |||||
String refResource = rule.getRefResource(); | |||||
int strategy = rule.getStrategy(); | |||||
if (StringUtil.isEmpty(refResource)) { | |||||
return null; | |||||
} | |||||
if (strategy == RuleConstant.STRATEGY_RELATE) { | |||||
return ClusterBuilderSlot.getClusterNode(refResource); | |||||
} | |||||
if (strategy == RuleConstant.STRATEGY_CHAIN) { | |||||
if (!refResource.equals(context.getName())) { | |||||
return null; | |||||
} | |||||
return node; | |||||
} | |||||
// No node. | |||||
return null; | |||||
} | |||||
private static boolean filterOrigin(String origin) { | |||||
// Origin cannot be `default` or `other`. | |||||
return !RuleConstant.LIMIT_APP_DEFAULT.equals(origin) && !RuleConstant.LIMIT_APP_OTHER.equals(origin); | |||||
} | |||||
static Node selectNodeByRequesterAndStrategy(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node) { | |||||
// The limit app should not be empty. | |||||
String limitApp = rule.getLimitApp(); | |||||
int strategy = rule.getStrategy(); | |||||
String origin = context.getOrigin(); | |||||
if (limitApp.equals(origin) && filterOrigin(origin)) { | |||||
if (strategy == RuleConstant.STRATEGY_DIRECT) { | |||||
// Matches limit origin, return origin statistic node. | |||||
return context.getOriginNode(); | |||||
} | |||||
return selectReferenceNode(rule, context, node); | |||||
} else if (RuleConstant.LIMIT_APP_DEFAULT.equals(limitApp)) { | |||||
if (strategy == RuleConstant.STRATEGY_DIRECT) { | |||||
// Return the cluster node. | |||||
return node.getClusterNode(); | |||||
} | |||||
return selectReferenceNode(rule, context, node); | |||||
} else if (RuleConstant.LIMIT_APP_OTHER.equals(limitApp) | |||||
&& FlowRuleManager.isOtherOrigin(origin, rule.getResource())) { | |||||
if (strategy == RuleConstant.STRATEGY_DIRECT) { | |||||
return context.getOriginNode(); | |||||
} | |||||
return selectReferenceNode(rule, context, node); | |||||
} | |||||
return null; | |||||
} | |||||
private FlowRuleChecker() {} | |||||
} |
@@ -26,14 +26,10 @@ import java.util.concurrent.TimeUnit; | |||||
import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; | import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; | ||||
import com.alibaba.csp.sentinel.log.RecordLog; | import com.alibaba.csp.sentinel.log.RecordLog; | ||||
import com.alibaba.csp.sentinel.util.StringUtil; | import com.alibaba.csp.sentinel.util.StringUtil; | ||||
import com.alibaba.csp.sentinel.context.Context; | |||||
import com.alibaba.csp.sentinel.node.DefaultNode; | |||||
import com.alibaba.csp.sentinel.node.metric.MetricTimerListener; | import com.alibaba.csp.sentinel.node.metric.MetricTimerListener; | ||||
import com.alibaba.csp.sentinel.property.DynamicSentinelProperty; | import com.alibaba.csp.sentinel.property.DynamicSentinelProperty; | ||||
import com.alibaba.csp.sentinel.property.PropertyListener; | import com.alibaba.csp.sentinel.property.PropertyListener; | ||||
import com.alibaba.csp.sentinel.property.SentinelProperty; | import com.alibaba.csp.sentinel.property.SentinelProperty; | ||||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; | |||||
import com.alibaba.csp.sentinel.slots.block.BlockException; | |||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant; | import com.alibaba.csp.sentinel.slots.block.RuleConstant; | ||||
import com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController; | import com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController; | ||||
import com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController; | import com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController; | ||||
@@ -118,7 +114,7 @@ public class FlowRuleManager { | |||||
rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT); | rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT); | ||||
} | } | ||||
Controller rater = new DefaultController(rule.getCount(), rule.getGrade()); | |||||
TrafficShapingController rater = new DefaultController(rule.getCount(), rule.getGrade()); | |||||
if (rule.getGrade() == RuleConstant.FLOW_GRADE_QPS | if (rule.getGrade() == RuleConstant.FLOW_GRADE_QPS | ||||
&& rule.getControlBehavior() == RuleConstant.CONTROL_BEHAVIOR_WARM_UP | && rule.getControlBehavior() == RuleConstant.CONTROL_BEHAVIOR_WARM_UP | ||||
&& rule.getWarmUpPeriodSec() > 0) { | && rule.getWarmUpPeriodSec() > 0) { | ||||
@@ -151,16 +147,8 @@ public class FlowRuleManager { | |||||
return newRuleMap; | return newRuleMap; | ||||
} | } | ||||
public static void checkFlow(ResourceWrapper resource, Context context, DefaultNode node, int count) | |||||
throws BlockException { | |||||
List<FlowRule> rules = flowRules.get(resource.getName()); | |||||
if (rules != null) { | |||||
for (FlowRule rule : rules) { | |||||
if (!rule.passCheck(context, node, count)) { | |||||
throw new FlowException(rule.getLimitApp()); | |||||
} | |||||
} | |||||
} | |||||
static Map<String, List<FlowRule>> getFlowRules() { | |||||
return flowRules; | |||||
} | } | ||||
public static boolean hasConfig(String resource) { | public static boolean hasConfig(String resource) { | ||||
@@ -232,6 +220,8 @@ public class FlowRuleManager { | |||||
return rule.getWarmUpPeriodSec() > 0; | return rule.getWarmUpPeriodSec() > 0; | ||||
case RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER: | case RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER: | ||||
return rule.getMaxQueueingTimeMs() > 0; | return rule.getMaxQueueingTimeMs() > 0; | ||||
case RuleConstant.CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER: | |||||
return rule.getWarmUpPeriodSec() > 0 && rule.getMaxQueueingTimeMs() > 0; | |||||
default: | default: | ||||
return true; | return true; | ||||
} | } | ||||
@@ -15,45 +15,64 @@ | |||||
*/ | */ | ||||
package com.alibaba.csp.sentinel.slots.block.flow; | package com.alibaba.csp.sentinel.slots.block.flow; | ||||
import java.util.List; | |||||
import java.util.Map; | |||||
import com.alibaba.csp.sentinel.context.Context; | import com.alibaba.csp.sentinel.context.Context; | ||||
import com.alibaba.csp.sentinel.node.DefaultNode; | import com.alibaba.csp.sentinel.node.DefaultNode; | ||||
import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; | import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; | ||||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; | import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; | ||||
import com.alibaba.csp.sentinel.slots.block.BlockException; | |||||
/** | /** | ||||
* <p> | * <p> | ||||
* Combined the runtime statistics collected from the previous | * Combined the runtime statistics collected from the previous | ||||
* slots(NodeSelectorSlot, ClusterNodeBuilderSlot, and StatistcSlot), FlowSlot | |||||
* slots (NodeSelectorSlot, ClusterNodeBuilderSlot, and StatisticSlot), FlowSlot | |||||
* will use pre-set rules to decide whether the incoming requests should be | * will use pre-set rules to decide whether the incoming requests should be | ||||
* blocked. | * blocked. | ||||
* </p> | |||||
* | * | ||||
* {@code SphU.entry (resourceName) }will throw FlowException if any rule is | |||||
* triggered. user can customize his own logic by catching FlowException. | |||||
* <p> | |||||
* {@code SphU.entry(resourceName)} will throw {@code FlowException} if any rule is | |||||
* triggered. Users can customize their own logic by catching {@code FlowException}. | |||||
* </p> | |||||
* | * | ||||
* <p> | |||||
* One resource can have multiple flow rules. FlowSlot traverses these rules | * One resource can have multiple flow rules. FlowSlot traverses these rules | ||||
* until one of them is triggered or all rules have been traversed. | * until one of them is triggered or all rules have been traversed. | ||||
* </p> | |||||
* | * | ||||
* Each FlowRule is mainly composed of the 2 factors: grade, strategy, path; we | |||||
* <p> | |||||
* Each {@link FlowRule} is mainly composed of these factors: grade, strategy, path. We | |||||
* can combine these factors to achieve different effects. | * can combine these factors to achieve different effects. | ||||
* </p> | |||||
* | * | ||||
* The grade is defined by the grade field in FlowRule. Here, 0 for thread | |||||
* isolation and 1 for request count shaping. Both thread count and request | |||||
* <p> | |||||
* The grade is defined by the {@code grade} field in {@link FlowRule}. Here, 0 for thread | |||||
* isolation and 1 for request count shaping (QPS). Both thread count and request | |||||
* count are collected in real runtime, and we can view these statistics by | * count are collected in real runtime, and we can view these statistics by | ||||
* following command: {@code | |||||
* curl http:// localhost:8719 / tree?type = root` | |||||
* idx id thread pass blocked success total aRt 1m-pass 1m-block 1m-all exeption | |||||
* following command: | |||||
* </p> | |||||
* | |||||
* <pre> | |||||
* curl http://localhost:8719/tree | |||||
* | |||||
* idx id thread pass blocked success total aRt 1m-pass 1m-block 1m-all exception | |||||
* 2 abc647 0 460 46 46 1 27 630 276 897 0 | * 2 abc647 0 460 46 46 1 27 630 276 897 0 | ||||
* } | |||||
* | |||||
* Thread for the count of threads that is currently processing the resource; | |||||
* pass for the count of incoming request within one second; blocked for the | |||||
* count of requests blocked within one second; success for the count of the | |||||
* requests successfully within one second; RT for the average response time of | |||||
* the requests within a second; total for the sum of incoming requests and | |||||
* blocked requests within one second; 1m-pass is for the count of incoming | |||||
* requests within one minute; 1m-block is for the count of a request blocked | |||||
* within one minute; 1m -all is the total of incoming and blocked requests | |||||
* within 1 minute; exception is for the count of exceptions in one second. | |||||
* </pre> | |||||
* | |||||
* <ul> | |||||
* <li>{@code thread} for the count of threads that is currently processing the resource</li> | |||||
* <li>{@code pass} for the count of incoming request within one second</li> | |||||
* <li>{@code blocked} for the count of requests blocked within one second</li> | |||||
* <li>{@code success} for the count of the requests successfully handled by Sentinel within one second</li> | |||||
* <li>{@code RT} for the average response time of the requests within a second</li> | |||||
* <li>{@code total} for the sum of incoming requests and blocked requests within one second</li> | |||||
* <li>{@code 1m-pass} is for the count of incoming requests within one minute</li> | |||||
* <li>{@code 1m-block} is for the count of a request blocked within one minute</li> | |||||
* <li>{@code 1m-all} is the total of incoming and blocked requests within one minute</li> | |||||
* <li>{@code exception} is for the count of business (customized) exceptions in one second</li> | |||||
* </ul> | |||||
* | * | ||||
* This stage is usually used to protect resources from occupying. If a resource | * This stage is usually used to protect resources from occupying. If a resource | ||||
* takes long time to finish, threads will begin to occupy. The longer the | * takes long time to finish, threads will begin to occupy. The longer the | ||||
@@ -71,57 +90,79 @@ import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; | |||||
* The benefit of using thread pool is that, it can walk away gracefully when | * The benefit of using thread pool is that, it can walk away gracefully when | ||||
* time out. But it also bring us the cost of context switch and additional | * time out. But it also bring us the cost of context switch and additional | ||||
* threads. If the incoming requests is already served in a separated thread, | * threads. If the incoming requests is already served in a separated thread, | ||||
* for instance, a servelet request, it will almost double the threads count if | |||||
* for instance, a Servlet HTTP request, it will almost double the threads count if | |||||
* using thread pool. | * using thread pool. | ||||
* | * | ||||
* ### QPS Shaping ### When qps exceeds the threshold, we will take actions to | |||||
* control the incoming request, and is configured by "controlBehavior" field in | |||||
* flowrule | |||||
* | |||||
* 1. immediately reject(RuleConstant.CONTROL_BEHAVIOR_DEFAULT) | |||||
* | |||||
* <h3>Traffic Shaping</h3> | |||||
* <p> | |||||
* When QPS exceeds the threshold, Sentinel will take actions to control the incoming request, | |||||
* and is configured by {@code controlBehavior} field in flow rules. | |||||
* </p> | |||||
* <ol> | |||||
* <li>Immediately reject ({@code RuleConstant.CONTROL_BEHAVIOR_DEFAULT})</li> | |||||
* <p> | |||||
* This is the default behavior. The exceeded request is rejected immediately | * This is the default behavior. The exceeded request is rejected immediately | ||||
* and the FlowException is thrown | * and the FlowException is thrown | ||||
* </p> | |||||
* | * | ||||
* 2. Warmup(RuleConstant.CONTROL_BEHAVIOR_WARM_UP) | |||||
* | |||||
* If the usage of system has been low for a while, and a large amount of | |||||
* <li>Warmup ({@code RuleConstant.CONTROL_BEHAVIOR_WARM_UP})</li> | |||||
* <p> | |||||
* If the load of system has been low for a while, and a large amount of | |||||
* requests comes, the system might not be able to handle all these requests at | * requests comes, the system might not be able to handle all these requests at | ||||
* once. However if we steady increase the incoming request, the system can warm | * once. However if we steady increase the incoming request, the system can warm | ||||
* up and finally be able to handle all the requests.If the usage of system has | |||||
* been low for a while, and a large amount of requests comes, the system might | |||||
* not be able to handle all these requests at once. However if we steady | |||||
* increase the incoming request, the system can warm up and finally be able to | |||||
* handle all the requests. This warmup period can be configured by setting the | |||||
* field "warmUpPeriodSec" in flow rule. | |||||
* | |||||
* 3.Rate limiter(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER) This strategy | |||||
* strictly controls the interval between requests. In other words, it allows | |||||
* requests to pass at a stable rate. | |||||
* <img src="https://github.com/alibaba/Sentinel/wiki/image/queue.gif" width= | |||||
* "300" height="200" /> This strategy is an implement of leaky bucket | |||||
* (https://en.wikipedia.org/wiki/Leaky_bucket). It is used to handle the | |||||
* request at a stable rate and is often used in burst traffic. For instance, | |||||
* Message. When a large number of requests beyond the system’s capacity arrive | |||||
* up and finally be able to handle all the requests. | |||||
* This warmup period can be configured by setting the field {@code warmUpPeriodSec} in flow rules. | |||||
* </p> | |||||
* | |||||
* <li>Uniform Rate Limiting ({@code RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER})</li> | |||||
* <p> | |||||
* This strategy strictly controls the interval between requests. | |||||
* In other words, it allows requests to pass at a stable, uniform rate. | |||||
* </p> | |||||
* <img src="https://raw.githubusercontent.com/wiki/alibaba/Sentinel/image/uniform-speed-queue.png" style="max-width: | |||||
* 60%;"/> | |||||
* <p> | |||||
* This strategy is an implement of <a href="https://en.wikipedia.org/wiki/Leaky_bucket">leaky bucket</a>. | |||||
* It is used to handle the request at a stable rate and is often used in burst traffic (e.g. message handling). | |||||
* When a large number of requests beyond the system’s capacity arrive | |||||
* at the same time, the system using this strategy will handle requests and its | * at the same time, the system using this strategy will handle requests and its | ||||
* fixed rate until all the requests have been processed or time out. | * fixed rate until all the requests have been processed or time out. | ||||
* </p> | |||||
* </ol> | |||||
* | * | ||||
* @author jialiang.linjl | * @author jialiang.linjl | ||||
* @author Eric Zhao | |||||
*/ | */ | ||||
public class FlowSlot extends AbstractLinkedProcessorSlot<DefaultNode> { | public class FlowSlot extends AbstractLinkedProcessorSlot<DefaultNode> { | ||||
@Override | @Override | ||||
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, Object... args) | public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, Object... args) | ||||
throws Throwable { | throws Throwable { | ||||
FlowRuleManager.checkFlow(resourceWrapper, context, node, count); | |||||
checkFlow(resourceWrapper, context, node, count); | |||||
fireEntry(context, resourceWrapper, node, count, args); | fireEntry(context, resourceWrapper, node, count, args); | ||||
} | } | ||||
void checkFlow(ResourceWrapper resource, Context context, DefaultNode node, int count) throws BlockException { | |||||
// Flow rule map cannot be null. | |||||
Map<String, List<FlowRule>> flowRules = FlowRuleManager.getFlowRules(); | |||||
List<FlowRule> rules = flowRules.get(resource.getName()); | |||||
if (rules != null) { | |||||
for (FlowRule rule : rules) { | |||||
if (!canPassCheck(rule, context, node, count)) { | |||||
throw new FlowException(rule.getLimitApp()); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
boolean canPassCheck(FlowRule rule, Context context, DefaultNode node, int count) { | |||||
return FlowRuleChecker.passCheck(rule, context, node, count); | |||||
} | |||||
@Override | @Override | ||||
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { | public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { | ||||
fireExit(context, resourceWrapper, count, args); | fireExit(context, resourceWrapper, count, args); | ||||
} | } | ||||
} | } |
@@ -18,10 +18,18 @@ package com.alibaba.csp.sentinel.slots.block.flow; | |||||
import com.alibaba.csp.sentinel.node.Node; | import com.alibaba.csp.sentinel.node.Node; | ||||
/** | /** | ||||
* A universal interface for traffic shaping controller. | |||||
* | |||||
* @author jialiang.linjl | * @author jialiang.linjl | ||||
*/ | */ | ||||
public interface Controller { | |||||
public interface TrafficShapingController { | |||||
/** | |||||
* Check whether given resource entry can pass with provided count. | |||||
* | |||||
* @param node resource node | |||||
* @param acquireCount count to acquire | |||||
* @return true if the resource entry can pass; false if it should be blocked | |||||
*/ | |||||
boolean canPass(Node node, int acquireCount); | boolean canPass(Node node, int acquireCount); | ||||
} | } |
@@ -17,18 +17,19 @@ package com.alibaba.csp.sentinel.slots.block.flow.controller; | |||||
import com.alibaba.csp.sentinel.node.Node; | import com.alibaba.csp.sentinel.node.Node; | ||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant; | import com.alibaba.csp.sentinel.slots.block.RuleConstant; | ||||
import com.alibaba.csp.sentinel.slots.block.flow.Controller; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.TrafficShapingController; | |||||
/** | /** | ||||
* Default throttling controller (immediately reject strategy). | |||||
* | |||||
* @author jialiang.linjl | * @author jialiang.linjl | ||||
*/ | */ | ||||
public class DefaultController implements Controller { | |||||
public class DefaultController implements TrafficShapingController { | |||||
double count = 0; | |||||
int grade = 0; | |||||
private double count; | |||||
private int grade; | |||||
public DefaultController(double count, int grade) { | public DefaultController(double count, int grade) { | ||||
super(); | |||||
this.count = count; | this.count = count; | ||||
this.grade = grade; | this.grade = grade; | ||||
} | } | ||||
@@ -17,7 +17,7 @@ package com.alibaba.csp.sentinel.slots.block.flow.controller; | |||||
import java.util.concurrent.atomic.AtomicLong; | import java.util.concurrent.atomic.AtomicLong; | ||||
import com.alibaba.csp.sentinel.slots.block.flow.Controller; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.TrafficShapingController; | |||||
import com.alibaba.csp.sentinel.util.TimeUtil; | import com.alibaba.csp.sentinel.util.TimeUtil; | ||||
import com.alibaba.csp.sentinel.node.Node; | import com.alibaba.csp.sentinel.node.Node; | ||||
@@ -25,7 +25,7 @@ import com.alibaba.csp.sentinel.node.Node; | |||||
/** | /** | ||||
* @author jialiang.linjl | * @author jialiang.linjl | ||||
*/ | */ | ||||
public class RateLimiterController implements Controller { | |||||
public class RateLimiterController implements TrafficShapingController { | |||||
private final int maxQueueingTimeMs; | private final int maxQueueingTimeMs; | ||||
private final double count; | private final double count; | ||||
@@ -19,17 +19,17 @@ import java.util.concurrent.atomic.AtomicLong; | |||||
import com.alibaba.csp.sentinel.util.TimeUtil; | import com.alibaba.csp.sentinel.util.TimeUtil; | ||||
import com.alibaba.csp.sentinel.node.Node; | import com.alibaba.csp.sentinel.node.Node; | ||||
import com.alibaba.csp.sentinel.slots.block.flow.Controller; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.TrafficShapingController; | |||||
/** | /** | ||||
* The principle idea comes from guava. However, the calculation of guava is | |||||
* rate-based, which means that we need to translate rate to qps. | |||||
* | |||||
* https://github.com/google/guava/blob/master/guava/src/com/google/common/util/concurrent/SmoothRateLimiter.java | |||||
* <p> | |||||
* The principle idea comes from Guava. However, the calculation of Guava is | |||||
* rate-based, which means that we need to translate rate to QPS. | |||||
* </p> | |||||
* | * | ||||
* Requests arriving at the pulse may drag down long idle systems even though it | * Requests arriving at the pulse may drag down long idle systems even though it | ||||
* has a much larger handling capability in stable period. It usually happens in | * has a much larger handling capability in stable period. It usually happens in | ||||
* scenarios that require extra time for initialization, for example, db | |||||
* scenarios that require extra time for initialization, e.g. DB | |||||
* establishes a connection; connects to a remote service, and so on. | * establishes a connection; connects to a remote service, and so on. | ||||
* | * | ||||
* That’s why we need “warm up”. | * That’s why we need “warm up”. | ||||
@@ -61,7 +61,7 @@ import com.alibaba.csp.sentinel.slots.block.flow.Controller; | |||||
* | * | ||||
* @author jialiang.linjl | * @author jialiang.linjl | ||||
*/ | */ | ||||
public class WarmUpController implements Controller { | |||||
public class WarmUpController implements TrafficShapingController { | |||||
protected double count; | protected double count; | ||||
private int coldFactor; | private int coldFactor; | ||||