* Add a new DegradeRule type, degrade by exception count in the last 60 seconds * Add demo about degrading by exception countmaster
@@ -15,6 +15,8 @@ | |||
*/ | |||
package com.alibaba.csp.sentinel.slots.block; | |||
import com.alibaba.csp.sentinel.node.IntervalProperty; | |||
/*** | |||
* @author youji.zj | |||
* @author jialiang.linjl | |||
@@ -25,7 +27,14 @@ public final class RuleConstant { | |||
public static final int FLOW_GRADE_QPS = 1; | |||
public static final int DEGRADE_GRADE_RT = 0; | |||
public static final int DEGRADE_GRADE_EXCEPTION = 1; | |||
/** | |||
* Degrade by biz exception ratio in the current {@link IntervalProperty#INTERVAL} second(s). | |||
*/ | |||
public static final int DEGRADE_GRADE_EXCEPTION_RATIO = 1; | |||
/** | |||
* Degrade by biz exception count in the last 60 seconds. | |||
*/ | |||
public static final int DEGRADE_GRADE_EXCEPTION_COUNT = 2; | |||
public static final int AUTHORITY_WHITE = 0; | |||
public static final int AUTHORITY_BLACK = 1; | |||
@@ -173,7 +173,7 @@ public class DegradeRule extends AbstractRule { | |||
if (passCount.incrementAndGet() < RT_MAX_EXCEED_N) { | |||
return true; | |||
} | |||
} else { | |||
} else if (grade == RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) { | |||
double exception = clusterNode.exceptionQps(); | |||
double success = clusterNode.successQps(); | |||
long total = clusterNode.totalQps(); | |||
@@ -190,6 +190,11 @@ public class DegradeRule extends AbstractRule { | |||
if (exception / success < count) { | |||
return true; | |||
} | |||
} else if (grade == RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) { | |||
double exception = clusterNode.totalException(); | |||
if (exception < count) { | |||
return true; | |||
} | |||
} | |||
synchronized (lock) { | |||
@@ -82,7 +82,7 @@ public class DegradeTest { | |||
rule.setCount(0.15); | |||
rule.setResource(key); | |||
rule.setTimeWindow(5); | |||
rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION); | |||
rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO); | |||
when(cn.successQps()).thenReturn(8L); | |||
@@ -97,4 +97,34 @@ public class DegradeTest { | |||
assertTrue(rule.passCheck(context, node, 1)); | |||
} | |||
@Test | |||
public void testExceptionCountModeDegrade() throws Throwable { | |||
String key = "test_degrade_exception_count"; | |||
ClusterNode cn = mock(ClusterNode.class); | |||
when(cn.totalException()).thenReturn(10L); | |||
ClusterBuilderSlot.getClusterNodeMap().put(new StringResourceWrapper(key, EntryType.IN), cn); | |||
Context context = mock(Context.class); | |||
DefaultNode node = mock(DefaultNode.class); | |||
when(node.getClusterNode()).thenReturn(cn); | |||
DegradeRule rule = new DegradeRule(); | |||
rule.setCount(4); | |||
rule.setResource(key); | |||
rule.setTimeWindow(2); | |||
rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT); | |||
when(cn.totalException()).thenReturn(4L); | |||
// Will fail. | |||
assertFalse(rule.passCheck(context, node, 1)); | |||
// Restore from the degrade timeout. | |||
TimeUnit.SECONDS.sleep(3); | |||
when(cn.totalException()).thenReturn(0L); | |||
// Will pass. | |||
assertTrue(rule.passCheck(context, node, 1)); | |||
} | |||
} |
@@ -0,0 +1,169 @@ | |||
package com.alibaba.csp.sentinel.demo.degrade; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import java.util.concurrent.TimeUnit; | |||
import java.util.concurrent.atomic.AtomicInteger; | |||
import com.alibaba.csp.sentinel.Entry; | |||
import com.alibaba.csp.sentinel.SphU; | |||
import com.alibaba.csp.sentinel.Tracer; | |||
import com.alibaba.csp.sentinel.slots.block.BlockException; | |||
import com.alibaba.csp.sentinel.slots.block.RuleConstant; | |||
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; | |||
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; | |||
import com.alibaba.csp.sentinel.util.TimeUtil; | |||
/** | |||
* <p> | |||
* Degrade is used when the resources are in an unstable state, these resources | |||
* will be degraded within the next defined time window. There are three ways to | |||
* measure whether a resource is stable or not: | |||
* <ul> | |||
* <li> | |||
* Exception count: When the exception count in the last 60 seconds greats than | |||
* or equals to the threshold, access to the resource will be blocked in the | |||
* coming time window. | |||
* </li> | |||
* <li> | |||
* Exception ratio, see {@link ExceptionRatioDegradeDemo}. | |||
* </li> | |||
* <li> | |||
* For average response time, see {@link RtDegradeDemo}. | |||
* </li> | |||
* </ul> | |||
* </p> | |||
* <p> | |||
* Note: When degrading by {@link RuleConstant#DEGRADE_GRADE_EXCEPTION_COUNT}, time window | |||
* less than 60 seconds will not work as expected. Because the exception count is | |||
* summed by minute, when a short time window elapsed, the degradation condition | |||
* may still be satisfied. | |||
* </p> | |||
* | |||
* @author Carpenter Lee | |||
*/ | |||
public class ExceptionCountDegradeDemo { | |||
private static final String KEY = "abc"; | |||
private static AtomicInteger total = new AtomicInteger(); | |||
private static AtomicInteger pass = new AtomicInteger(); | |||
private static AtomicInteger block = new AtomicInteger(); | |||
private static AtomicInteger bizException = new AtomicInteger(); | |||
private static volatile boolean stop = false; | |||
private static final int threadCount = 1; | |||
private static int seconds = 60 + 40; | |||
public static void main(String[] args) throws Exception { | |||
tick(); | |||
initDegradeRule(); | |||
for (int i = 0; i < threadCount; i++) { | |||
Thread entryThread = new Thread(new Runnable() { | |||
@Override | |||
public void run() { | |||
int count = 0; | |||
while (true) { | |||
count++; | |||
Entry entry = null; | |||
try { | |||
Thread.sleep(20); | |||
entry = SphU.entry(KEY); | |||
// token acquired, means pass | |||
pass.addAndGet(1); | |||
if (count % 2 == 0) { | |||
// biz code raise an exception. | |||
throw new RuntimeException("throw runtime "); | |||
} | |||
} catch (BlockException e) { | |||
block.addAndGet(1); | |||
} catch (Throwable t) { | |||
bizException.incrementAndGet(); | |||
Tracer.trace(t); | |||
} finally { | |||
total.addAndGet(1); | |||
if (entry != null) { | |||
entry.exit(); | |||
} | |||
} | |||
} | |||
} | |||
}); | |||
entryThread.setName("working-thread"); | |||
entryThread.start(); | |||
} | |||
} | |||
private static void initDegradeRule() { | |||
List<DegradeRule> rules = new ArrayList<DegradeRule>(); | |||
DegradeRule rule = new DegradeRule(); | |||
rule.setResource(KEY); | |||
// set limit exception count to 4 | |||
rule.setCount(4); | |||
rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT); | |||
/** | |||
* When degrading by {@link RuleConstant#DEGRADE_GRADE_EXCEPTION_COUNT}, time window | |||
* less than 60 seconds will not work as expected. Because the exception count is | |||
* summed by minute, when a short time window elapsed, the degradation condition | |||
* may still be satisfied. | |||
*/ | |||
rule.setTimeWindow(10); | |||
rules.add(rule); | |||
DegradeRuleManager.loadRules(rules); | |||
} | |||
private static void tick() { | |||
Thread timer = new Thread(new TimerTask()); | |||
timer.setName("sentinel-timer-task"); | |||
timer.start(); | |||
} | |||
static class TimerTask implements Runnable { | |||
@Override | |||
public void run() { | |||
long start = System.currentTimeMillis(); | |||
System.out.println("begin to statistic!!!"); | |||
long oldTotal = 0; | |||
long oldPass = 0; | |||
long oldBlock = 0; | |||
long oldBizException = 0; | |||
while (!stop) { | |||
try { | |||
TimeUnit.SECONDS.sleep(1); | |||
} catch (InterruptedException e) { | |||
} | |||
long globalTotal = total.get(); | |||
long oneSecondTotal = globalTotal - oldTotal; | |||
oldTotal = globalTotal; | |||
long globalPass = pass.get(); | |||
long oneSecondPass = globalPass - oldPass; | |||
oldPass = globalPass; | |||
long globalBlock = block.get(); | |||
long oneSecondBlock = globalBlock - oldBlock; | |||
oldBlock = globalBlock; | |||
long globalBizException = bizException.get(); | |||
long oneSecondBizException = globalBizException - oldBizException; | |||
oldBizException = globalBizException; | |||
System.out.println(TimeUtil.currentTimeMillis() + ", oneSecondTotal:" + oneSecondTotal | |||
+ ", oneSecondPass:" + oneSecondPass | |||
+ ", oneSecondBlock:" + oneSecondBlock | |||
+ ", oneSecondBizException:" + oneSecondBizException); | |||
if (seconds-- <= 0) { | |||
stop = true; | |||
} | |||
} | |||
long cost = System.currentTimeMillis() - start; | |||
System.out.println("time cost: " + cost + " ms"); | |||
System.out.println("total:" + total.get() + ", pass:" + pass.get() | |||
+ ", block:" + block.get() + ", bizException:" + bizException.get()); | |||
System.exit(0); | |||
} | |||
} | |||
} |
@@ -32,13 +32,16 @@ import com.alibaba.csp.sentinel.util.TimeUtil; | |||
/** | |||
* <p> | |||
* Degrade is used when the resources are in an unstable state, these resources | |||
* will be degraded within the next defined time window. There are two ways to | |||
* will be degraded within the next defined time window. There are three ways to | |||
* measure whether a resource is stable or not: | |||
* <ul> | |||
* <li> | |||
* Exception ratio: When the ratio of exception count per second and the success | |||
* qps exceeds the threshold , access to the resource will be blocked in the | |||
* coming window. | |||
* qps greats than or equals to the threshold, access to the resource will be blocked | |||
* in the coming time window. | |||
* </li> | |||
* <li> | |||
* Exception Count, see {@link ExceptionCountDegradeDemo}. | |||
* </li> | |||
* <li> | |||
* For average response time, see {@link RtDegradeDemo}. | |||
@@ -110,7 +113,7 @@ public class ExceptionRatioDegradeDemo { | |||
rule.setResource(KEY); | |||
// set limit exception ratio to 0.1 | |||
rule.setCount(0.1); | |||
rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION); | |||
rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO); | |||
rule.setTimeWindow(10); | |||
rules.add(rule); | |||
DegradeRuleManager.loadRules(rules); | |||
@@ -35,8 +35,8 @@ import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; | |||
* <ul> | |||
* <li> | |||
* Average Response Time ('DegradeRule.Grade=RuleContants.DEGRADE_GRADE_RT'): When the | |||
* average RT exceeds the threshold ('count' in 'DegradeRule', ms), the resource | |||
* enters a quasi-degraded state. If the RT of next coming five requests still | |||
* average RT greats than or equals to the threshold ('count' in 'DegradeRule', ms), the | |||
* resource enters a quasi-degraded state. If the RT of next coming five requests still | |||
* exceed this threshold, this resource will be downgraded, which means that in | |||
* the next time window(Defined in 'timeWindow', s units) all the access to this | |||
* resource will be blocked. | |||
@@ -44,6 +44,9 @@ import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; | |||
* <li> | |||
* Exception ratio, see {@link ExceptionRatioDegradeDemo}. | |||
* </li> | |||
* <li> | |||
* Exception Count, see {@link ExceptionCountDegradeDemo}. | |||
* </li> | |||
* </ul> | |||
* | |||
* </p> | |||