Refactor degrade hierarchy with new circuit breaker mechanism and improve strategy
* Add `CircuitBreaker` abstraction (with half-open state) and add circuit breaker state change event observer support. * Improve circuit breaking strategy (avg RT → slow request ratio) and make statistics of each rule dependent (to support arbitrary statistic interval). * Add simple "trial" mechanism (aka. half-open). * Refactor mechanism of metric recording and state change handling for circuit breakers: record RT and error when requests have completed (i.e. `onExit`, based on #1420). Signed-off-by: Eric Zhao <sczyh16@gmail.com>
This commit is contained in:
parent
17c3ff7a44
commit
c0c27c86b7
|
|
@ -15,19 +15,12 @@
|
||||||
*/
|
*/
|
||||||
package com.alibaba.csp.sentinel.slots.block.degrade;
|
package com.alibaba.csp.sentinel.slots.block.degrade;
|
||||||
|
|
||||||
import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory;
|
|
||||||
import com.alibaba.csp.sentinel.context.Context;
|
import com.alibaba.csp.sentinel.context.Context;
|
||||||
import com.alibaba.csp.sentinel.node.ClusterNode;
|
|
||||||
import com.alibaba.csp.sentinel.node.DefaultNode;
|
import com.alibaba.csp.sentinel.node.DefaultNode;
|
||||||
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;
|
|
||||||
|
|
||||||
import java.util.concurrent.Executors;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
|
|
@ -52,13 +45,10 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @author jialiang.linjl
|
* @author jialiang.linjl
|
||||||
|
* @author Eric Zhao
|
||||||
*/
|
*/
|
||||||
public class DegradeRule extends AbstractRule {
|
public class DegradeRule extends AbstractRule {
|
||||||
|
|
||||||
@SuppressWarnings("PMD.ThreadPoolCreationRule")
|
|
||||||
private static ScheduledExecutorService pool = Executors.newScheduledThreadPool(
|
|
||||||
Runtime.getRuntime().availableProcessors(), new NamedThreadFactory("sentinel-degrade-reset-task", true));
|
|
||||||
|
|
||||||
public DegradeRule() {}
|
public DegradeRule() {}
|
||||||
|
|
||||||
public DegradeRule(String resourceName) {
|
public DegradeRule(String resourceName) {
|
||||||
|
|
@ -66,26 +56,20 @@ public class DegradeRule extends AbstractRule {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RT threshold or exception ratio threshold count.
|
* Circuit breaking strategy (0: average RT, 1: exception ratio, 2: exception count).
|
||||||
*/
|
|
||||||
private double count;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Degrade recover timeout (in seconds) when degradation occurs.
|
|
||||||
*/
|
|
||||||
private int timeWindow;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Degrade strategy (0: average RT, 1: exception ratio, 2: exception count).
|
|
||||||
*/
|
*/
|
||||||
private int grade = RuleConstant.DEGRADE_GRADE_RT;
|
private int grade = RuleConstant.DEGRADE_GRADE_RT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Minimum number of consecutive slow requests that can trigger RT circuit breaking.
|
* Threshold count.
|
||||||
*
|
|
||||||
* @since 1.7.0
|
|
||||||
*/
|
*/
|
||||||
private int rtSlowRequestAmount = RuleConstant.DEGRADE_DEFAULT_SLOW_REQUEST_AMOUNT;
|
private double count;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recovery timeout (in seconds) when circuit breaker opens. After the timeout, the circuit breaker will
|
||||||
|
* transform to half-open state for trying a few requests.
|
||||||
|
*/
|
||||||
|
private int timeWindow;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Minimum number of requests (in an active statistic time span) that can trigger circuit breaking.
|
* Minimum number of requests (in an active statistic time span) that can trigger circuit breaking.
|
||||||
|
|
@ -94,6 +78,13 @@ public class DegradeRule extends AbstractRule {
|
||||||
*/
|
*/
|
||||||
private int minRequestAmount = RuleConstant.DEGRADE_DEFAULT_MIN_REQUEST_AMOUNT;
|
private int minRequestAmount = RuleConstant.DEGRADE_DEFAULT_MIN_REQUEST_AMOUNT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The threshold of slow request ratio in RT mode.
|
||||||
|
*/
|
||||||
|
private double slowRatioThreshold = 1.0d;
|
||||||
|
|
||||||
|
private int statIntervalMs = 1000;
|
||||||
|
|
||||||
public int getGrade() {
|
public int getGrade() {
|
||||||
return grade;
|
return grade;
|
||||||
}
|
}
|
||||||
|
|
@ -121,15 +112,6 @@ public class DegradeRule extends AbstractRule {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getRtSlowRequestAmount() {
|
|
||||||
return rtSlowRequestAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DegradeRule setRtSlowRequestAmount(int rtSlowRequestAmount) {
|
|
||||||
this.rtSlowRequestAmount = rtSlowRequestAmount;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMinRequestAmount() {
|
public int getMinRequestAmount() {
|
||||||
return minRequestAmount;
|
return minRequestAmount;
|
||||||
}
|
}
|
||||||
|
|
@ -139,28 +121,42 @@ public class DegradeRule extends AbstractRule {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public double getSlowRatioThreshold() {
|
||||||
|
return slowRatioThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DegradeRule setSlowRatioThreshold(double slowRatioThreshold) {
|
||||||
|
this.slowRatioThreshold = slowRatioThreshold;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStatIntervalMs() {
|
||||||
|
return statIntervalMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DegradeRule setStatIntervalMs(int statIntervalMs) {
|
||||||
|
this.statIntervalMs = statIntervalMs;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) { return true; }
|
if (this == o) { return true; }
|
||||||
if (o == null || getClass() != o.getClass()) { return false; }
|
if (o == null || getClass() != o.getClass()) { return false; }
|
||||||
if (!super.equals(o)) { return false; }
|
if (!super.equals(o)) { return false; }
|
||||||
DegradeRule that = (DegradeRule) o;
|
DegradeRule rule = (DegradeRule)o;
|
||||||
return Double.compare(that.count, count) == 0 &&
|
return Double.compare(rule.count, count) == 0 &&
|
||||||
timeWindow == that.timeWindow &&
|
timeWindow == rule.timeWindow &&
|
||||||
grade == that.grade &&
|
grade == rule.grade &&
|
||||||
rtSlowRequestAmount == that.rtSlowRequestAmount &&
|
minRequestAmount == rule.minRequestAmount &&
|
||||||
minRequestAmount == that.minRequestAmount;
|
Double.compare(rule.slowRatioThreshold, slowRatioThreshold) == 0 &&
|
||||||
|
statIntervalMs == rule.statIntervalMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int result = super.hashCode();
|
return Objects.hash(super.hashCode(), count, timeWindow, grade, minRequestAmount,
|
||||||
result = 31 * result + new Double(count).hashCode();
|
slowRatioThreshold, statIntervalMs);
|
||||||
result = 31 * result + timeWindow;
|
|
||||||
result = 31 * result + grade;
|
|
||||||
result = 31 * result + rtSlowRequestAmount;
|
|
||||||
result = 31 * result + minRequestAmount;
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -171,84 +167,15 @@ public class DegradeRule extends AbstractRule {
|
||||||
", count=" + count +
|
", count=" + count +
|
||||||
", limitApp=" + getLimitApp() +
|
", limitApp=" + getLimitApp() +
|
||||||
", timeWindow=" + timeWindow +
|
", timeWindow=" + timeWindow +
|
||||||
", rtSlowRequestAmount=" + rtSlowRequestAmount +
|
|
||||||
", minRequestAmount=" + minRequestAmount +
|
", minRequestAmount=" + minRequestAmount +
|
||||||
"}";
|
", slowRatioThreshold=" + slowRatioThreshold +
|
||||||
}
|
", statIntervalMs=" + statIntervalMs +
|
||||||
|
'}';
|
||||||
// Internal implementation (will be deprecated and moved outside).
|
|
||||||
|
|
||||||
private AtomicLong passCount = new AtomicLong(0);
|
|
||||||
private final AtomicBoolean cut = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean passCheck(Context context, DefaultNode node, int acquireCount, Object... args) {
|
|
||||||
if (cut.get()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ClusterNode clusterNode = ClusterBuilderSlot.getClusterNode(this.getResource());
|
|
||||||
if (clusterNode == null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (grade == RuleConstant.DEGRADE_GRADE_RT) {
|
|
||||||
double rt = clusterNode.avgRt();
|
|
||||||
if (rt < this.count) {
|
|
||||||
passCount.set(0);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sentinel will degrade the service only if count exceeds.
|
|
||||||
if (passCount.incrementAndGet() < rtSlowRequestAmount) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else if (grade == RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) {
|
|
||||||
double exception = clusterNode.exceptionQps();
|
|
||||||
double success = clusterNode.successQps();
|
|
||||||
double total = clusterNode.totalQps();
|
|
||||||
// If total amount is less than minRequestAmount, the request will pass.
|
|
||||||
if (total < minRequestAmount) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// In the same aligned statistic time window,
|
|
||||||
// "success" (aka. completed count) = exception count + non-exception count (realSuccess)
|
|
||||||
double realSuccess = success - exception;
|
|
||||||
if (realSuccess <= 0 && exception < minRequestAmount) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exception / success < count) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else if (grade == RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) {
|
|
||||||
double exception = clusterNode.totalException();
|
|
||||||
if (exception < count) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cut.compareAndSet(false, true)) {
|
|
||||||
ResetTask resetTask = new ResetTask(this);
|
|
||||||
pool.schedule(resetTask, timeWindow, TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class ResetTask implements Runnable {
|
|
||||||
|
|
||||||
private DegradeRule rule;
|
|
||||||
|
|
||||||
ResetTask(DegradeRule rule) {
|
|
||||||
this.rule = rule;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
@Deprecated
|
||||||
rule.passCount.set(0);
|
public boolean passCheck(Context context, DefaultNode node, int count, Object... args) {
|
||||||
rule.cut.set(false);
|
return false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,29 +21,29 @@ import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
import com.alibaba.csp.sentinel.config.SentinelConfig;
|
|
||||||
import com.alibaba.csp.sentinel.context.Context;
|
|
||||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||||
import com.alibaba.csp.sentinel.node.DefaultNode;
|
|
||||||
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.degrade.circuitbreaker.CircuitBreaker;
|
||||||
|
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.ExceptionCircuitBreaker;
|
||||||
|
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.ResponseTimeCircuitBreaker;
|
||||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* The rule manager for circuit breaking rules ({@link DegradeRule}).
|
||||||
|
*
|
||||||
* @author youji.zj
|
* @author youji.zj
|
||||||
* @author jialiang.linjl
|
* @author jialiang.linjl
|
||||||
* @author Eric Zhao
|
* @author Eric Zhao
|
||||||
*/
|
*/
|
||||||
public final class DegradeRuleManager {
|
public final class DegradeRuleManager {
|
||||||
|
|
||||||
private static final Map<String, Set<DegradeRule>> degradeRules = new ConcurrentHashMap<>();
|
private static volatile Map<String, List<CircuitBreaker>> circuitBreakers = new HashMap<>();
|
||||||
|
private static volatile Map<String, Set<DegradeRule>> ruleMap = new HashMap<>();
|
||||||
|
|
||||||
private static final RulePropertyListener LISTENER = new RulePropertyListener();
|
private static final RulePropertyListener LISTENER = new RulePropertyListener();
|
||||||
private static SentinelProperty<List<DegradeRule>> currentProperty
|
private static SentinelProperty<List<DegradeRule>> currentProperty
|
||||||
|
|
@ -69,41 +69,37 @@ public final class DegradeRuleManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void checkDegrade(ResourceWrapper resource, Context context, DefaultNode node, int count)
|
static List<CircuitBreaker> getCircuitBreakers(String resourceName) {
|
||||||
throws BlockException {
|
return circuitBreakers.get(resourceName);
|
||||||
|
|
||||||
Set<DegradeRule> rules = degradeRules.get(resource.getName());
|
|
||||||
if (rules == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (DegradeRule rule : rules) {
|
|
||||||
if (!rule.passCheck(context, node, count)) {
|
|
||||||
throw new DegradeException(rule.getLimitApp(), rule);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean hasConfig(String resource) {
|
public static boolean hasConfig(String resource) {
|
||||||
if (resource == null) {
|
if (resource == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return degradeRules.containsKey(resource);
|
return circuitBreakers.containsKey(resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a copy of the rules.
|
* <p>Get existing circuit breaking rules.</p>
|
||||||
|
* <p>Note: DO NOT modify the rules from the returned list directly.
|
||||||
|
* The behavior is <strong>undefined</strong>.</p>
|
||||||
*
|
*
|
||||||
* @return a new copy of the rules.
|
* @return list of existing circuit breaking rules, or empty list if no rules were loaded
|
||||||
*/
|
*/
|
||||||
public static List<DegradeRule> getRules() {
|
public static List<DegradeRule> getRules() {
|
||||||
List<DegradeRule> rules = new ArrayList<>();
|
List<DegradeRule> rules = new ArrayList<>();
|
||||||
for (Map.Entry<String, Set<DegradeRule>> entry : degradeRules.entrySet()) {
|
for (Map.Entry<String, Set<DegradeRule>> entry : ruleMap.entrySet()) {
|
||||||
rules.addAll(entry.getValue());
|
rules.addAll(entry.getValue());
|
||||||
}
|
}
|
||||||
return rules;
|
return rules;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Set<DegradeRule> getRulesOfResource(String resource) {
|
||||||
|
AssertUtil.assertNotBlank(resource, "resource name cannot be blank");
|
||||||
|
return ruleMap.get(resource);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load {@link DegradeRule}s, former rules will be replaced.
|
* Load {@link DegradeRule}s, former rules will be replaced.
|
||||||
*
|
*
|
||||||
|
|
@ -113,7 +109,7 @@ public final class DegradeRuleManager {
|
||||||
try {
|
try {
|
||||||
currentProperty.updateValue(rules);
|
currentProperty.updateValue(rules);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
RecordLog.warn("[DegradeRuleManager] Unexpected error when loading degrade rules", e);
|
RecordLog.error("[DegradeRuleManager] Unexpected error when loading degrade rules", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -128,7 +124,7 @@ public final class DegradeRuleManager {
|
||||||
public static boolean setRulesForResource(String resourceName, Set<DegradeRule> rules) {
|
public static boolean setRulesForResource(String resourceName, Set<DegradeRule> rules) {
|
||||||
AssertUtil.notEmpty(resourceName, "resourceName cannot be empty");
|
AssertUtil.notEmpty(resourceName, "resourceName cannot be empty");
|
||||||
try {
|
try {
|
||||||
Map<String, Set<DegradeRule>> newRuleMap = new HashMap<>(degradeRules);
|
Map<String, Set<DegradeRule>> newRuleMap = new HashMap<>(ruleMap);
|
||||||
if (rules == null) {
|
if (rules == null) {
|
||||||
newRuleMap.remove(resourceName);
|
newRuleMap.remove(resourceName);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -146,62 +142,41 @@ public final class DegradeRuleManager {
|
||||||
}
|
}
|
||||||
return currentProperty.updateValue(allRules);
|
return currentProperty.updateValue(allRules);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
RecordLog.warn(
|
RecordLog.error("[DegradeRuleManager] Unexpected error when setting circuit breaking"
|
||||||
"[DegradeRuleManager] Unexpected error when setting degrade rules for resource: " + resourceName, e);
|
+ " rules for resource: " + resourceName, e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class RulePropertyListener implements PropertyListener<List<DegradeRule>> {
|
private static CircuitBreaker getExistingSameCbOrNew(/*@Valid*/ DegradeRule rule) {
|
||||||
|
List<CircuitBreaker> cbs = getCircuitBreakers(rule.getResource());
|
||||||
@Override
|
if (cbs == null || cbs.isEmpty()) {
|
||||||
public void configUpdate(List<DegradeRule> conf) {
|
return newCircuitBreakerFrom(rule);
|
||||||
Map<String, Set<DegradeRule>> rules = loadDegradeConf(conf);
|
|
||||||
if (rules != null) {
|
|
||||||
degradeRules.clear();
|
|
||||||
degradeRules.putAll(rules);
|
|
||||||
}
|
}
|
||||||
RecordLog.info("[DegradeRuleManager] Degrade rules received: " + degradeRules);
|
for (CircuitBreaker cb : cbs) {
|
||||||
|
if (rule.equals(cb.getRule())) {
|
||||||
|
// Reuse the circuit breaker if the rule remains unchanged.
|
||||||
|
return cb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newCircuitBreakerFrom(rule);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public void configLoad(List<DegradeRule> conf) {
|
* Create a circuit breaker instance from provided circuit breaking rule.
|
||||||
Map<String, Set<DegradeRule>> rules = loadDegradeConf(conf);
|
*
|
||||||
if (rules != null) {
|
* @param rule a valid circuit breaking rule
|
||||||
degradeRules.clear();
|
* @return new circuit breaker based on provided rule; null if rule is invalid or unsupported type
|
||||||
degradeRules.putAll(rules);
|
*/
|
||||||
}
|
private static CircuitBreaker newCircuitBreakerFrom(/*@Valid*/ DegradeRule rule) {
|
||||||
RecordLog.info("[DegradeRuleManager] Degrade rules loaded: " + degradeRules);
|
switch (rule.getGrade()) {
|
||||||
}
|
case RuleConstant.DEGRADE_GRADE_RT:
|
||||||
|
return new ResponseTimeCircuitBreaker(rule);
|
||||||
private Map<String, Set<DegradeRule>> loadDegradeConf(List<DegradeRule> list) {
|
case RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO:
|
||||||
Map<String, Set<DegradeRule>> newRuleMap = new ConcurrentHashMap<>();
|
case RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT:
|
||||||
|
return new ExceptionCircuitBreaker(rule);
|
||||||
if (list == null || list.isEmpty()) {
|
default:
|
||||||
return newRuleMap;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
for (DegradeRule rule : list) {
|
|
||||||
if (!isValidRule(rule)) {
|
|
||||||
RecordLog.warn(
|
|
||||||
"[DegradeRuleManager] Ignoring invalid degrade rule when loading new rules: " + rule);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (StringUtil.isBlank(rule.getLimitApp())) {
|
|
||||||
rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
|
|
||||||
}
|
|
||||||
|
|
||||||
String identity = rule.getResource();
|
|
||||||
Set<DegradeRule> ruleSet = newRuleMap.get(identity);
|
|
||||||
if (ruleSet == null) {
|
|
||||||
ruleSet = new HashSet<>();
|
|
||||||
newRuleMap.put(identity, ruleSet);
|
|
||||||
}
|
|
||||||
ruleSet.add(rule);
|
|
||||||
}
|
|
||||||
|
|
||||||
return newRuleMap;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -211,23 +186,83 @@ public final class DegradeRuleManager {
|
||||||
if (!baseValid) {
|
if (!baseValid) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
int maxAllowedRt = SentinelConfig.statisticMaxRt();
|
if (rule.getMinRequestAmount() <= 0 || rule.getStatIntervalMs() <= 0) {
|
||||||
if (rule.getGrade() == RuleConstant.DEGRADE_GRADE_RT) {
|
|
||||||
if (rule.getRtSlowRequestAmount() <= 0) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Warn for RT mode that exceeds the {@code TIME_DROP_VALVE}.
|
switch (rule.getGrade()) {
|
||||||
if (rule.getCount() > maxAllowedRt) {
|
case RuleConstant.DEGRADE_GRADE_RT:
|
||||||
RecordLog.warn(String.format("[DegradeRuleManager] WARN: setting large RT threshold (%.1f ms)"
|
return rule.getSlowRatioThreshold() >= 0 && rule.getSlowRatioThreshold() <= 1;
|
||||||
+ " in RT mode will not take effect since it exceeds the max allowed value (%d ms)",
|
case RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO:
|
||||||
rule.getCount(), maxAllowedRt));
|
return rule.getCount() <= 1;
|
||||||
|
case RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check exception ratio mode.
|
private static class RulePropertyListener implements PropertyListener<List<DegradeRule>> {
|
||||||
if (rule.getGrade() == RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) {
|
|
||||||
return rule.getCount() <= 1 && rule.getMinRequestAmount() > 0;
|
private synchronized void reloadFrom(List<DegradeRule> list) {
|
||||||
|
Map<String, List<CircuitBreaker>> cbs = buildCircuitBreakers(list);
|
||||||
|
Map<String, Set<DegradeRule>> rm = new HashMap<>(cbs.size());
|
||||||
|
|
||||||
|
for (Map.Entry<String, List<CircuitBreaker>> e : cbs.entrySet()) {
|
||||||
|
assert e.getValue() != null && !e.getValue().isEmpty();
|
||||||
|
|
||||||
|
Set<DegradeRule> rules = new HashSet<>(e.getValue().size());
|
||||||
|
for (CircuitBreaker cb : e.getValue()) {
|
||||||
|
rules.add(cb.getRule());
|
||||||
|
}
|
||||||
|
rm.put(e.getKey(), rules);
|
||||||
|
}
|
||||||
|
|
||||||
|
DegradeRuleManager.circuitBreakers = cbs;
|
||||||
|
DegradeRuleManager.ruleMap = rm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configUpdate(List<DegradeRule> conf) {
|
||||||
|
reloadFrom(conf);
|
||||||
|
RecordLog.info("[DegradeRuleManager] Degrade rules has been updated to: " + ruleMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configLoad(List<DegradeRule> conf) {
|
||||||
|
reloadFrom(conf);
|
||||||
|
RecordLog.info("[DegradeRuleManager] Degrade rules loaded: " + ruleMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, List<CircuitBreaker>> buildCircuitBreakers(List<DegradeRule> list) {
|
||||||
|
Map<String, List<CircuitBreaker>> cbMap = new HashMap<>(8);
|
||||||
|
if (list == null || list.isEmpty()) {
|
||||||
|
return cbMap;
|
||||||
|
}
|
||||||
|
for (DegradeRule rule : list) {
|
||||||
|
if (!isValidRule(rule)) {
|
||||||
|
RecordLog.warn("[DegradeRuleManager] Ignoring invalid rule when loading new rules: " + rule);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtil.isBlank(rule.getLimitApp())) {
|
||||||
|
rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
|
||||||
|
}
|
||||||
|
CircuitBreaker cb = getExistingSameCbOrNew(rule);
|
||||||
|
if (cb == null) {
|
||||||
|
RecordLog.warn("[DegradeRuleManager] Unknown circuit breaking strategy, ignoring: " + rule);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String resourceName = rule.getResource();
|
||||||
|
|
||||||
|
List<CircuitBreaker> cbList = cbMap.get(resourceName);
|
||||||
|
if (cbList == null) {
|
||||||
|
cbList = new ArrayList<>();
|
||||||
|
cbMap.put(resourceName, cbList);
|
||||||
|
}
|
||||||
|
cbList.add(cb);
|
||||||
|
}
|
||||||
|
return cbMap;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,30 +15,73 @@
|
||||||
*/
|
*/
|
||||||
package com.alibaba.csp.sentinel.slots.block.degrade;
|
package com.alibaba.csp.sentinel.slots.block.degrade;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.alibaba.csp.sentinel.Entry;
|
||||||
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.ProcessorSlot;
|
import com.alibaba.csp.sentinel.slotchain.ProcessorSlot;
|
||||||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
|
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
|
||||||
|
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||||
|
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker;
|
||||||
import com.alibaba.csp.sentinel.spi.SpiOrder;
|
import com.alibaba.csp.sentinel.spi.SpiOrder;
|
||||||
|
import com.alibaba.csp.sentinel.util.TimeUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link ProcessorSlot} dedicates to {@link DegradeRule} checking.
|
* A {@link ProcessorSlot} dedicates to circuit breaking.
|
||||||
*
|
*
|
||||||
* @author leyou
|
* @author Carpenter Lee
|
||||||
|
* @author Eric Zhao
|
||||||
*/
|
*/
|
||||||
@SpiOrder(-1000)
|
@SpiOrder(-1000)
|
||||||
public class DegradeSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
|
public class DegradeSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args)
|
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
|
||||||
throws Throwable {
|
boolean prioritized, Object... args) throws Throwable {
|
||||||
DegradeRuleManager.checkDegrade(resourceWrapper, context, node, count);
|
performChecking(resourceWrapper);
|
||||||
|
|
||||||
fireEntry(context, resourceWrapper, node, count, prioritized, args);
|
fireEntry(context, resourceWrapper, node, count, prioritized, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void performChecking(ResourceWrapper r) throws BlockException {
|
||||||
|
List<CircuitBreaker> circuitBreakers = DegradeRuleManager.getCircuitBreakers(r.getName());
|
||||||
|
if (circuitBreakers == null || circuitBreakers.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (CircuitBreaker cb : circuitBreakers) {
|
||||||
|
if (!cb.tryPass()) {
|
||||||
|
throw new DegradeException(cb.getRule().getLimitApp(), cb.getRule());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
|
public void exit(Context context, ResourceWrapper r, int count, Object... args) {
|
||||||
fireExit(context, resourceWrapper, count, args);
|
Entry curEntry = context.getCurEntry();
|
||||||
|
if (curEntry.getBlockError() != null) {
|
||||||
|
fireExit(context, r, count, args);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<CircuitBreaker> circuitBreakers = DegradeRuleManager.getCircuitBreakers(r.getName());
|
||||||
|
if (circuitBreakers == null || circuitBreakers.isEmpty()) {
|
||||||
|
fireExit(context, r, count, args);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curEntry.getBlockError() == null) {
|
||||||
|
long completeTime = curEntry.getCompleteTimestamp();
|
||||||
|
if (completeTime <= 0) {
|
||||||
|
completeTime = TimeUtil.currentTimeMillis();
|
||||||
|
}
|
||||||
|
long rt = completeTime - curEntry.getCreateTimestamp();
|
||||||
|
Throwable error = curEntry.getError();
|
||||||
|
for (CircuitBreaker circuitBreaker : circuitBreakers) {
|
||||||
|
circuitBreaker.onRequestComplete(rt, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fireExit(context, r, count, args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,147 @@
|
||||||
|
/*
|
||||||
|
* Copyright 1999-2019 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.degrade.circuitbreaker;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
|
||||||
|
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
|
||||||
|
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||||
|
import com.alibaba.csp.sentinel.util.TimeUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Eric Zhao
|
||||||
|
* @since 1.8.0
|
||||||
|
*/
|
||||||
|
public abstract class AbstractCircuitBreaker implements CircuitBreaker {
|
||||||
|
|
||||||
|
protected final DegradeRule rule;
|
||||||
|
protected final int recoveryTimeoutMs;
|
||||||
|
|
||||||
|
private final EventObserverRegistry observerRegistry;
|
||||||
|
|
||||||
|
protected final AtomicReference<State> currentState = new AtomicReference<>(State.CLOSED);
|
||||||
|
protected volatile long nextRetryTimestamp;
|
||||||
|
|
||||||
|
public AbstractCircuitBreaker(DegradeRule rule) {
|
||||||
|
this(rule, EventObserverRegistry.getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
AbstractCircuitBreaker(DegradeRule rule, EventObserverRegistry observerRegistry) {
|
||||||
|
AssertUtil.notNull(observerRegistry, "observerRegistry cannot be null");
|
||||||
|
if (!DegradeRuleManager.isValidRule(rule)) {
|
||||||
|
throw new IllegalArgumentException("Invalid DegradeRule: " + rule);
|
||||||
|
}
|
||||||
|
this.observerRegistry = observerRegistry;
|
||||||
|
this.rule = rule;
|
||||||
|
this.recoveryTimeoutMs = rule.getTimeWindow() * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DegradeRule getRule() {
|
||||||
|
return rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public State currentState() {
|
||||||
|
return currentState.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean tryPass() {
|
||||||
|
// Template implementation.
|
||||||
|
if (currentState.get() == State.CLOSED) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (currentState.get() == State.OPEN) {
|
||||||
|
// For half-open state we allow a request for trial.
|
||||||
|
return retryTimeoutArrived() && fromOpenToHalfOpen();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the statistic data.
|
||||||
|
*/
|
||||||
|
abstract void resetStat();
|
||||||
|
|
||||||
|
protected boolean retryTimeoutArrived() {
|
||||||
|
return TimeUtil.currentTimeMillis() >= nextRetryTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateNextRetryTimestamp() {
|
||||||
|
this.nextRetryTimestamp = TimeUtil.currentTimeMillis() + recoveryTimeoutMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean fromCloseToOpen(double snapshotValue) {
|
||||||
|
State prev = State.CLOSED;
|
||||||
|
if (currentState.compareAndSet(prev, State.OPEN)) {
|
||||||
|
updateNextRetryTimestamp();
|
||||||
|
|
||||||
|
for (CircuitBreakerStateChangeObserver observer : observerRegistry.getStateChangeObservers()) {
|
||||||
|
observer.onStateChange(prev, State.OPEN, rule, snapshotValue);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean fromOpenToHalfOpen() {
|
||||||
|
if (currentState.compareAndSet(State.OPEN, State.HALF_OPEN)) {
|
||||||
|
for (CircuitBreakerStateChangeObserver observer : observerRegistry.getStateChangeObservers()) {
|
||||||
|
observer.onStateChange(State.OPEN, State.HALF_OPEN, rule, null);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean fromHalfOpenToOpen(double snapshotValue) {
|
||||||
|
if (currentState.compareAndSet(State.HALF_OPEN, State.OPEN)) {
|
||||||
|
updateNextRetryTimestamp();
|
||||||
|
for (CircuitBreakerStateChangeObserver observer : observerRegistry.getStateChangeObservers()) {
|
||||||
|
observer.onStateChange(State.HALF_OPEN, State.OPEN, rule, snapshotValue);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean fromHalfOpenToClose() {
|
||||||
|
if (currentState.compareAndSet(State.HALF_OPEN, State.CLOSED)) {
|
||||||
|
resetStat();
|
||||||
|
for (CircuitBreakerStateChangeObserver observer : observerRegistry.getStateChangeObservers()) {
|
||||||
|
observer.onStateChange(State.HALF_OPEN, State.CLOSED, rule, null);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void transformToOpen(double triggerValue) {
|
||||||
|
State cs = currentState.get();
|
||||||
|
switch (cs) {
|
||||||
|
case CLOSED:
|
||||||
|
fromCloseToOpen(triggerValue);
|
||||||
|
break;
|
||||||
|
case HALF_OPEN:
|
||||||
|
fromHalfOpenToOpen(triggerValue);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
/*
|
||||||
|
* Copyright 1999-2019 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
|
||||||
|
*
|
||||||
|
* https://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.degrade.circuitbreaker;
|
||||||
|
|
||||||
|
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Basic <a href="https://martinfowler.com/bliki/CircuitBreaker.html">circuit breaker</a> interface.</p>
|
||||||
|
*
|
||||||
|
* @author Eric Zhao
|
||||||
|
*/
|
||||||
|
public interface CircuitBreaker {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the associated circuit breaking rule.
|
||||||
|
*
|
||||||
|
* @return associated circuit breaking rule
|
||||||
|
*/
|
||||||
|
DegradeRule getRule();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Acquires permission of an invocation only if it is available at the time of invocation.
|
||||||
|
*
|
||||||
|
* @return {@code true} if permission was acquired and {@code false} otherwise
|
||||||
|
*/
|
||||||
|
boolean tryPass();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current state of the circuit breaker.
|
||||||
|
*
|
||||||
|
* @return current state of the circuit breaker
|
||||||
|
*/
|
||||||
|
State currentState();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Record a completed request with the given response time and error (if present) and
|
||||||
|
* handle state transformation of the circuit breaker.
|
||||||
|
*
|
||||||
|
* @param rt the response time of this entry
|
||||||
|
* @param error the error of this entry (if present)
|
||||||
|
*/
|
||||||
|
void onRequestComplete(long rt, Throwable error);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Circuit breaker state.
|
||||||
|
*/
|
||||||
|
enum State {
|
||||||
|
/**
|
||||||
|
* In {@code OPEN} state, all requests will be rejected until the next recovery time point.
|
||||||
|
*/
|
||||||
|
OPEN,
|
||||||
|
/**
|
||||||
|
* In {@code HALF_OPEN} state, the circuit breaker will allow a "probe" invocation.
|
||||||
|
* If the invocation is abnormal according to the strategy (e.g. it's slow), the circuit breaker
|
||||||
|
* will re-transform to the {@code OPEN} state and wait for the next recovery time point;
|
||||||
|
* otherwise the resource will be regarded as "recovered" and the circuit breaker
|
||||||
|
* will cease cutting off requests and transform to {@code CLOSED} state.
|
||||||
|
*/
|
||||||
|
HALF_OPEN,
|
||||||
|
/**
|
||||||
|
* In {@code CLOSED} state, all requests are permitted. When current metric value exceeds the threshold,
|
||||||
|
* the circuit breaker will transform to {@code OPEN} state.
|
||||||
|
*/
|
||||||
|
CLOSED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* Copyright 1999-2019 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.degrade.circuitbreaker;
|
||||||
|
|
||||||
|
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Eric Zhao
|
||||||
|
* @since 1.8.0
|
||||||
|
*/
|
||||||
|
public interface CircuitBreakerStateChangeObserver {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Observer method triggered when circuit breaker state changed. The transformation could be:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>From {@code CLOSED} to {@code OPEN} (with the triggered metric)</li>
|
||||||
|
* <li>From {@code OPEN} to {@code HALF_OPEN}</li>
|
||||||
|
* <li>From {@code OPEN} to {@code CLOSED}</li>
|
||||||
|
* <li>From {@code HALF_OPEN} to {@code OPEN} (with the triggered metric)</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param prevState previous state of the circuit breaker
|
||||||
|
* @param newState new state of the circuit breaker
|
||||||
|
* @param rule associated rule
|
||||||
|
* @param snapshotValue triggered value on circuit breaker opens (null if the new state is CLOSED or HALF_OPEN)
|
||||||
|
*/
|
||||||
|
void onStateChange(CircuitBreaker.State prevState, CircuitBreaker.State newState, DegradeRule rule,
|
||||||
|
Double snapshotValue);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* Copyright 1999-2020 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
|
||||||
|
*
|
||||||
|
* https://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.degrade.circuitbreaker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Eric Zhao
|
||||||
|
* @since 1.8.0
|
||||||
|
*/
|
||||||
|
public enum CircuitBreakerStrategy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Circuit breaker opens (cuts off) when slow request ratio exceeds the threshold.
|
||||||
|
*/
|
||||||
|
SLOW_REQUEST_RATIO(0),
|
||||||
|
/**
|
||||||
|
* Circuit breaker opens (cuts off) when error ratio exceeds the threshold.
|
||||||
|
*/
|
||||||
|
ERROR_RATIO(1),
|
||||||
|
/**
|
||||||
|
* Circuit breaker opens (cuts off) when error count exceeds the threshold.
|
||||||
|
*/
|
||||||
|
ERROR_COUNT(2);
|
||||||
|
|
||||||
|
private int type;
|
||||||
|
|
||||||
|
CircuitBreakerStrategy(int type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* Copyright 1999-2020 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
|
||||||
|
*
|
||||||
|
* https://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.degrade.circuitbreaker;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Registry for circuit breaker event observers.</p>
|
||||||
|
*
|
||||||
|
* @author Eric Zhao
|
||||||
|
* @since 1.8.0
|
||||||
|
*/
|
||||||
|
public class EventObserverRegistry {
|
||||||
|
|
||||||
|
private final Map<String, CircuitBreakerStateChangeObserver> stateChangeObserverMap = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a circuit breaker state change observer.
|
||||||
|
*
|
||||||
|
* @param name observer name
|
||||||
|
* @param observer a valid observer
|
||||||
|
*/
|
||||||
|
public void addStateChangeObserver(String name, CircuitBreakerStateChangeObserver observer) {
|
||||||
|
AssertUtil.notNull(name, "name cannot be null");
|
||||||
|
AssertUtil.notNull(observer, "observer cannot be null");
|
||||||
|
stateChangeObserverMap.put(name, observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeStateChangeObserver(String name) {
|
||||||
|
AssertUtil.notNull(name, "name cannot be null");
|
||||||
|
return stateChangeObserverMap.remove(name) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all registered state chane observers.
|
||||||
|
*
|
||||||
|
* @return all registered state chane observers
|
||||||
|
*/
|
||||||
|
public List<CircuitBreakerStateChangeObserver> getStateChangeObservers() {
|
||||||
|
return new ArrayList<>(stateChangeObserverMap.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EventObserverRegistry getInstance() {
|
||||||
|
return InstanceHolder.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class InstanceHolder {
|
||||||
|
private static EventObserverRegistry instance = new EventObserverRegistry();
|
||||||
|
}
|
||||||
|
|
||||||
|
EventObserverRegistry() {}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
/*
|
||||||
|
* Copyright 1999-2019 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.degrade.circuitbreaker;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
|
||||||
|
import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray;
|
||||||
|
import com.alibaba.csp.sentinel.slots.statistic.base.LongAdder;
|
||||||
|
import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap;
|
||||||
|
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||||
|
|
||||||
|
import static com.alibaba.csp.sentinel.slots.block.RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO;
|
||||||
|
import static com.alibaba.csp.sentinel.slots.block.RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Eric Zhao
|
||||||
|
* @since 1.8.0
|
||||||
|
*/
|
||||||
|
public class ExceptionCircuitBreaker extends AbstractCircuitBreaker {
|
||||||
|
|
||||||
|
private final int strategy;
|
||||||
|
private final int minRequestAmount;
|
||||||
|
private final double threshold;
|
||||||
|
|
||||||
|
private final LeapArray<SimpleErrorCounter> stat;
|
||||||
|
|
||||||
|
public ExceptionCircuitBreaker(DegradeRule rule) {
|
||||||
|
this(rule, new SimpleErrorCounterLeapArray(1, rule.getStatIntervalMs()));
|
||||||
|
}
|
||||||
|
|
||||||
|
ExceptionCircuitBreaker(DegradeRule rule, LeapArray<SimpleErrorCounter> stat) {
|
||||||
|
super(rule);
|
||||||
|
this.strategy = rule.getGrade();
|
||||||
|
boolean modeOk = strategy == DEGRADE_GRADE_EXCEPTION_RATIO || strategy == DEGRADE_GRADE_EXCEPTION_COUNT;
|
||||||
|
AssertUtil.isTrue(modeOk, "rule strategy should be error-ratio or error-count");
|
||||||
|
AssertUtil.notNull(stat, "stat cannot be null");
|
||||||
|
this.minRequestAmount = rule.getMinRequestAmount();
|
||||||
|
this.threshold = rule.getCount();
|
||||||
|
this.stat = stat;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void resetStat() {
|
||||||
|
// Reset current bucket (bucket count = 1).
|
||||||
|
stat.currentWindow().value().reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestComplete(long rt, Throwable error) {
|
||||||
|
SimpleErrorCounter counter = stat.currentWindow().value();
|
||||||
|
if (error != null) {
|
||||||
|
counter.getErrorCount().add(1);
|
||||||
|
}
|
||||||
|
counter.getTotalCount().add(1);
|
||||||
|
|
||||||
|
handleStateChangeWhenThresholdExceeded(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleStateChangeWhenThresholdExceeded(Throwable error) {
|
||||||
|
if (currentState.get() == State.OPEN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (currentState.get() == State.HALF_OPEN) {
|
||||||
|
if (error == null) {
|
||||||
|
fromHalfOpenToClose();
|
||||||
|
} else {
|
||||||
|
fromHalfOpenToOpen(1.0d);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<SimpleErrorCounter> counters = stat.values();
|
||||||
|
long errCount = 0;
|
||||||
|
long totalCount = 0;
|
||||||
|
for (SimpleErrorCounter counter : counters) {
|
||||||
|
errCount += counter.errorCount.sum();
|
||||||
|
totalCount += counter.totalCount.sum();
|
||||||
|
}
|
||||||
|
if (totalCount < minRequestAmount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
double curCount = errCount;
|
||||||
|
if (strategy == DEGRADE_GRADE_EXCEPTION_RATIO) {
|
||||||
|
// Use errorRatio
|
||||||
|
curCount = errCount * 1.0d / totalCount;
|
||||||
|
}
|
||||||
|
if (curCount > threshold) {
|
||||||
|
transformToOpen(curCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class SimpleErrorCounter {
|
||||||
|
private LongAdder errorCount;
|
||||||
|
private LongAdder totalCount;
|
||||||
|
|
||||||
|
public SimpleErrorCounter() {
|
||||||
|
this.errorCount = new LongAdder();
|
||||||
|
this.totalCount = new LongAdder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LongAdder getErrorCount() {
|
||||||
|
return errorCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LongAdder getTotalCount() {
|
||||||
|
return totalCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleErrorCounter reset() {
|
||||||
|
errorCount.reset();
|
||||||
|
totalCount.reset();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "SimpleErrorCounter{" +
|
||||||
|
"errorCount=" + errorCount +
|
||||||
|
", totalCount=" + totalCount +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class SimpleErrorCounterLeapArray extends LeapArray<SimpleErrorCounter> {
|
||||||
|
|
||||||
|
public SimpleErrorCounterLeapArray(int sampleCount, int intervalInMs) {
|
||||||
|
super(sampleCount, intervalInMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SimpleErrorCounter newEmptyBucket(long timeMillis) {
|
||||||
|
return new SimpleErrorCounter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected WindowWrap<SimpleErrorCounter> resetWindowTo(WindowWrap<SimpleErrorCounter> w, long startTime) {
|
||||||
|
// Update the start time and reset value.
|
||||||
|
w.resetTo(startTime);
|
||||||
|
w.value().reset();
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
/*
|
||||||
|
* Copyright 1999-2019 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.degrade.circuitbreaker;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
|
||||||
|
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
|
||||||
|
import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray;
|
||||||
|
import com.alibaba.csp.sentinel.slots.statistic.base.LongAdder;
|
||||||
|
import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap;
|
||||||
|
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Eric Zhao
|
||||||
|
* @since 1.8.0
|
||||||
|
*/
|
||||||
|
public class ResponseTimeCircuitBreaker extends AbstractCircuitBreaker {
|
||||||
|
|
||||||
|
private final long maxAllowedRt;
|
||||||
|
private final double maxSlowRequestRatio;
|
||||||
|
private final int minRequestAmount;
|
||||||
|
|
||||||
|
private final LeapArray<SlowRequestCounter> slidingCounter;
|
||||||
|
|
||||||
|
public ResponseTimeCircuitBreaker(DegradeRule rule) {
|
||||||
|
this(rule, new SlowRequestLeapArray(1, rule.getStatIntervalMs()));
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseTimeCircuitBreaker(DegradeRule rule, LeapArray<SlowRequestCounter> stat) {
|
||||||
|
super(rule);
|
||||||
|
AssertUtil.isTrue(rule.getGrade() == RuleConstant.DEGRADE_GRADE_RT, "rule metric type should be RT");
|
||||||
|
AssertUtil.notNull(stat, "stat cannot be null");
|
||||||
|
this.maxAllowedRt = Math.round(rule.getCount());
|
||||||
|
this.maxSlowRequestRatio = rule.getSlowRatioThreshold();
|
||||||
|
this.minRequestAmount = rule.getMinRequestAmount();
|
||||||
|
this.slidingCounter = stat;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resetStat() {
|
||||||
|
// Reset current bucket (bucket count = 1).
|
||||||
|
slidingCounter.currentWindow().value().reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestComplete(long rt, Throwable error) {
|
||||||
|
SlowRequestCounter counter = slidingCounter.currentWindow().value();
|
||||||
|
if (rt > maxAllowedRt) {
|
||||||
|
counter.slowCount.add(1);
|
||||||
|
}
|
||||||
|
counter.totalCount.add(1);
|
||||||
|
|
||||||
|
handleStateChangeWhenThresholdExceeded(rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleStateChangeWhenThresholdExceeded(long rt) {
|
||||||
|
if (currentState.get() == State.OPEN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (currentState.get() == State.HALF_OPEN) {
|
||||||
|
// TODO: improve logic for half-open recovery
|
||||||
|
if (rt > maxAllowedRt) {
|
||||||
|
fromHalfOpenToOpen(1.0d);
|
||||||
|
} else {
|
||||||
|
fromHalfOpenToClose();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<SlowRequestCounter> counters = slidingCounter.values();
|
||||||
|
long slowCount = 0;
|
||||||
|
long totalCount = 0;
|
||||||
|
for (SlowRequestCounter counter : counters) {
|
||||||
|
slowCount += counter.slowCount.sum();
|
||||||
|
totalCount += counter.totalCount.sum();
|
||||||
|
}
|
||||||
|
if (totalCount < minRequestAmount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
double currentRatio = slowCount * 1.0d / totalCount;
|
||||||
|
if (currentRatio > maxSlowRequestRatio) {
|
||||||
|
transformToOpen(currentRatio);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class SlowRequestCounter {
|
||||||
|
private LongAdder slowCount;
|
||||||
|
private LongAdder totalCount;
|
||||||
|
|
||||||
|
public SlowRequestCounter() {
|
||||||
|
this.slowCount = new LongAdder();
|
||||||
|
this.totalCount = new LongAdder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LongAdder getSlowCount() {
|
||||||
|
return slowCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LongAdder getTotalCount() {
|
||||||
|
return totalCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SlowRequestCounter reset() {
|
||||||
|
slowCount.reset();
|
||||||
|
totalCount.reset();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "SlowRequestCounter{" +
|
||||||
|
"slowCount=" + slowCount +
|
||||||
|
", totalCount=" + totalCount +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class SlowRequestLeapArray extends LeapArray<SlowRequestCounter> {
|
||||||
|
|
||||||
|
public SlowRequestLeapArray(int sampleCount, int intervalInMs) {
|
||||||
|
super(sampleCount, intervalInMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SlowRequestCounter newEmptyBucket(long timeMillis) {
|
||||||
|
return new SlowRequestCounter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected WindowWrap<SlowRequestCounter> resetWindowTo(WindowWrap<SlowRequestCounter> w, long startTime) {
|
||||||
|
w.resetTo(startTime);
|
||||||
|
w.value().reset();
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue