Skip to content

Commit f6d8ac9

Browse files
sczyh30mastertiller
authored andcommitted
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 alibaba#1420). Signed-off-by: Eric Zhao <[email protected]>
1 parent ce37639 commit f6d8ac9

10 files changed

+901
-206
lines changed

sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRule.java

+46-119
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,12 @@
1515
*/
1616
package com.alibaba.csp.sentinel.slots.block.degrade;
1717

18-
import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory;
1918
import com.alibaba.csp.sentinel.context.Context;
20-
import com.alibaba.csp.sentinel.node.ClusterNode;
2119
import com.alibaba.csp.sentinel.node.DefaultNode;
2220
import com.alibaba.csp.sentinel.slots.block.AbstractRule;
2321
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
24-
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;
2522

26-
import java.util.concurrent.Executors;
27-
import java.util.concurrent.ScheduledExecutorService;
28-
import java.util.concurrent.TimeUnit;
29-
import java.util.concurrent.atomic.AtomicBoolean;
30-
import java.util.concurrent.atomic.AtomicLong;
23+
import java.util.Objects;
3124

3225
/**
3326
* <p>
@@ -52,47 +45,45 @@
5245
* </ul>
5346
*
5447
* @author jialiang.linjl
48+
* @author Eric Zhao
5549
*/
5650
public class DegradeRule extends AbstractRule {
5751

58-
@SuppressWarnings("PMD.ThreadPoolCreationRule")
59-
private static ScheduledExecutorService pool = Executors.newScheduledThreadPool(
60-
Runtime.getRuntime().availableProcessors(), new NamedThreadFactory("sentinel-degrade-reset-task", true));
61-
6252
public DegradeRule() {}
6353

6454
public DegradeRule(String resourceName) {
6555
setResource(resourceName);
6656
}
6757

6858
/**
69-
* RT threshold or exception ratio threshold count.
59+
* Circuit breaking strategy (0: average RT, 1: exception ratio, 2: exception count).
7060
*/
71-
private double count;
61+
private int grade = RuleConstant.DEGRADE_GRADE_RT;
7262

7363
/**
74-
* Degrade recover timeout (in seconds) when degradation occurs.
64+
* Threshold count.
7565
*/
76-
private int timeWindow;
66+
private double count;
7767

7868
/**
79-
* Degrade strategy (0: average RT, 1: exception ratio, 2: exception count).
69+
* Recovery timeout (in seconds) when circuit breaker opens. After the timeout, the circuit breaker will
70+
* transform to half-open state for trying a few requests.
8071
*/
81-
private int grade = RuleConstant.DEGRADE_GRADE_RT;
72+
private int timeWindow;
8273

8374
/**
84-
* Minimum number of consecutive slow requests that can trigger RT circuit breaking.
75+
* Minimum number of requests (in an active statistic time span) that can trigger circuit breaking.
8576
*
8677
* @since 1.7.0
8778
*/
88-
private int rtSlowRequestAmount = RuleConstant.DEGRADE_DEFAULT_SLOW_REQUEST_AMOUNT;
79+
private int minRequestAmount = RuleConstant.DEGRADE_DEFAULT_MIN_REQUEST_AMOUNT;
8980

9081
/**
91-
* Minimum number of requests (in an active statistic time span) that can trigger circuit breaking.
92-
*
93-
* @since 1.7.0
82+
* The threshold of slow request ratio in RT mode.
9483
*/
95-
private int minRequestAmount = RuleConstant.DEGRADE_DEFAULT_MIN_REQUEST_AMOUNT;
84+
private double slowRatioThreshold = 1.0d;
85+
86+
private int statIntervalMs = 1000;
9687

9788
public int getGrade() {
9889
return grade;
@@ -121,21 +112,30 @@ public DegradeRule setTimeWindow(int timeWindow) {
121112
return this;
122113
}
123114

124-
public int getRtSlowRequestAmount() {
125-
return rtSlowRequestAmount;
115+
public int getMinRequestAmount() {
116+
return minRequestAmount;
126117
}
127118

128-
public DegradeRule setRtSlowRequestAmount(int rtSlowRequestAmount) {
129-
this.rtSlowRequestAmount = rtSlowRequestAmount;
119+
public DegradeRule setMinRequestAmount(int minRequestAmount) {
120+
this.minRequestAmount = minRequestAmount;
130121
return this;
131122
}
132123

133-
public int getMinRequestAmount() {
134-
return minRequestAmount;
124+
public double getSlowRatioThreshold() {
125+
return slowRatioThreshold;
135126
}
136127

137-
public DegradeRule setMinRequestAmount(int minRequestAmount) {
138-
this.minRequestAmount = minRequestAmount;
128+
public DegradeRule setSlowRatioThreshold(double slowRatioThreshold) {
129+
this.slowRatioThreshold = slowRatioThreshold;
130+
return this;
131+
}
132+
133+
public int getStatIntervalMs() {
134+
return statIntervalMs;
135+
}
136+
137+
public DegradeRule setStatIntervalMs(int statIntervalMs) {
138+
this.statIntervalMs = statIntervalMs;
139139
return this;
140140
}
141141

@@ -144,23 +144,19 @@ public boolean equals(Object o) {
144144
if (this == o) { return true; }
145145
if (o == null || getClass() != o.getClass()) { return false; }
146146
if (!super.equals(o)) { return false; }
147-
DegradeRule that = (DegradeRule) o;
148-
return Double.compare(that.count, count) == 0 &&
149-
timeWindow == that.timeWindow &&
150-
grade == that.grade &&
151-
rtSlowRequestAmount == that.rtSlowRequestAmount &&
152-
minRequestAmount == that.minRequestAmount;
147+
DegradeRule rule = (DegradeRule)o;
148+
return Double.compare(rule.count, count) == 0 &&
149+
timeWindow == rule.timeWindow &&
150+
grade == rule.grade &&
151+
minRequestAmount == rule.minRequestAmount &&
152+
Double.compare(rule.slowRatioThreshold, slowRatioThreshold) == 0 &&
153+
statIntervalMs == rule.statIntervalMs;
153154
}
154155

155156
@Override
156157
public int hashCode() {
157-
int result = super.hashCode();
158-
result = 31 * result + new Double(count).hashCode();
159-
result = 31 * result + timeWindow;
160-
result = 31 * result + grade;
161-
result = 31 * result + rtSlowRequestAmount;
162-
result = 31 * result + minRequestAmount;
163-
return result;
158+
return Objects.hash(super.hashCode(), count, timeWindow, grade, minRequestAmount,
159+
slowRatioThreshold, statIntervalMs);
164160
}
165161

166162
@Override
@@ -171,84 +167,15 @@ public String toString() {
171167
", count=" + count +
172168
", limitApp=" + getLimitApp() +
173169
", timeWindow=" + timeWindow +
174-
", rtSlowRequestAmount=" + rtSlowRequestAmount +
175170
", minRequestAmount=" + minRequestAmount +
176-
"}";
171+
", slowRatioThreshold=" + slowRatioThreshold +
172+
", statIntervalMs=" + statIntervalMs +
173+
'}';
177174
}
178175

179-
// Internal implementation (will be deprecated and moved outside).
180-
181-
private AtomicLong passCount = new AtomicLong(0);
182-
private final AtomicBoolean cut = new AtomicBoolean(false);
183-
184176
@Override
185-
public boolean passCheck(Context context, DefaultNode node, int acquireCount, Object... args) {
186-
if (cut.get()) {
187-
return false;
188-
}
189-
190-
ClusterNode clusterNode = ClusterBuilderSlot.getClusterNode(this.getResource());
191-
if (clusterNode == null) {
192-
return true;
193-
}
194-
195-
if (grade == RuleConstant.DEGRADE_GRADE_RT) {
196-
double rt = clusterNode.avgRt();
197-
if (rt < this.count) {
198-
passCount.set(0);
199-
return true;
200-
}
201-
202-
// Sentinel will degrade the service only if count exceeds.
203-
if (passCount.incrementAndGet() < rtSlowRequestAmount) {
204-
return true;
205-
}
206-
} else if (grade == RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) {
207-
double exception = clusterNode.exceptionQps();
208-
double success = clusterNode.successQps();
209-
double total = clusterNode.totalQps();
210-
// If total amount is less than minRequestAmount, the request will pass.
211-
if (total < minRequestAmount) {
212-
return true;
213-
}
214-
215-
// In the same aligned statistic time window,
216-
// "success" (aka. completed count) = exception count + non-exception count (realSuccess)
217-
double realSuccess = success - exception;
218-
if (realSuccess <= 0 && exception < minRequestAmount) {
219-
return true;
220-
}
221-
222-
if (exception / success < count) {
223-
return true;
224-
}
225-
} else if (grade == RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) {
226-
double exception = clusterNode.totalException();
227-
if (exception < count) {
228-
return true;
229-
}
230-
}
231-
232-
if (cut.compareAndSet(false, true)) {
233-
ResetTask resetTask = new ResetTask(this);
234-
pool.schedule(resetTask, timeWindow, TimeUnit.SECONDS);
235-
}
236-
177+
@Deprecated
178+
public boolean passCheck(Context context, DefaultNode node, int count, Object... args) {
237179
return false;
238180
}
239-
240-
private static final class ResetTask implements Runnable {
241-
242-
private DegradeRule rule;
243-
244-
ResetTask(DegradeRule rule) {
245-
this.rule = rule;
246-
}
247-
248-
@Override
249-
public void run() {
250-
rule.passCount.set(0);
251-
rule.cut.set(false);
252-
}
253-
}
254181
}

0 commit comments

Comments
 (0)