Sentinel 源码分析- 熔断降级原理分析
直接从Sentinel 源码demo ExceptionRatioCircuitBreakerDemo
看起
直接看他的main函数
public static void main(String[] args) throws Exception {
initDegradeRule();
......
final int concurrency = 8;
for (int i = 0; i < concurrency; i++) {
Thread entryThread = new Thread(() -> {
while (true) {
Entry entry = null;
try {
entry = SphU.entry(KEY);
sleep(ThreadLocalRandom.current().nextInt(5, 10));
pass.addAndGet(1);
// Error probability is 45%
if (ThreadLocalRandom.current().nextInt(0, 100) > 55) {
// biz code raise an exception.
throw new RuntimeException("oops");
}
} catch (BlockException e) {
block.addAndGet(1);
sleep(ThreadLocalRandom.current().nextInt(5, 10));
} catch (Throwable t) {
bizException.incrementAndGet();
// It's required to record exception here manually.
Tracer.traceEntry(t, entry);
} finally {
total.addAndGet(1);
if (entry != null) {
entry.exit();
}
}
}
});
entryThread.setName("sentinel-simulate-traffic-task-" + i);
entryThread.start();
}
}
从源码中可以看出,比如限流那一套多了一个在catch Throwable的操作 Tracer.traceEntry(t, entry);
看一下其中的源码
public static void traceEntry(Throwable e, Entry entry) {
if (!shouldTrace(e)) {
return;
}
traceEntryInternal(e, entry);
}
private static void traceEntryInternal(/*@NeedToTrace*/ Throwable e, Entry entry) {
if (entry == null) {
return;
}
entry.setError(e);
}
// Entry.java
public void setError(Throwable error) {
this.error = error;
}
从源码可以看出 这里仅仅是将error赋给entry的成员error。这里是为了后面的finally逻辑做铺垫
异常被触发并执行完catch逻辑后,会执行finally的逻辑,会调用entry.exit();
方法。
//CtEntry.java
protected void exitForContext(Context context, int count, Object... args) throws ErrorEntryFreeException {
if (context != null) {
// Null context should exit without clean-up.
if (context instanceof NullContext) {
return;
}
if (context.getCurEntry() != this) {
String curEntryNameInContext = context.getCurEntry() == null ? null
: context.getCurEntry().getResourceWrapper().getName();
// Clean previous call stack.
CtEntry e = (CtEntry) context.getCurEntry();
while (e != null) {
e.exit(count, args);
e = (CtEntry) e.parent;
}
String errorMessage = String.format("The order of entry exit can't be paired with the order of entry"
+ ", current entry in context: <%s>, but expected: <%s>", curEntryNameInContext,
resourceWrapper.getName());
throw new ErrorEntryFreeException(errorMessage);
} else {
// Go through the onExit hook of all slots.
if (chain != null) {
//最终会调用到chain的exit
chain.exit(context, resourceWrapper, count, args);
}
// Go through the existing terminate handlers (associated to this invocation).
callExitHandlersAndCleanUp(context);
// Restore the call stack.
context.setCurEntry(parent);
if (parent != null) {
((CtEntry) parent).child = null;
}
if (parent == null) {
// Default context (auto entered) will be exited automatically.
if (ContextUtil.isDefaultContext(context)) {
ContextUtil.exit();
}
}
// Clean the reference of context in current entry to avoid duplicate exit.
clearEntryContext();
}
}
}
最终会执行exitForContext
,并调用到chain.exit(context, resourceWrapper, count, args);
, 于是会调用到DegradeSlot
的 exit
@Override
public void exit(Context context, ResourceWrapper r, int count, Object... 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) {
// passed request
for (CircuitBreaker circuitBreaker : circuitBreakers) {
//这里是最关键的一步
circuitBreaker.onRequestComplete(context);
}
}
fireExit(context, r, count, args);
}
这里的 circleBreaker 是 ExceptionCircuitBreaker 对象,最终会调用
@Override
public void onRequestComplete(Context context) {
Entry entry = context.getCurEntry();
if (entry == null) {
return;
}
// 这里和上面的entry缓存error就关联上了,发现错误并计数
Throwable error = entry.getError();
SimpleErrorCounter counter = stat.currentWindow().value();
if (error != null) {
counter.getErrorCount().add(1);
}
counter.getTotalCount().add(1);
//核心函数,根据统计,继续状态切换 close -> open (--> open -> half-open)
handleStateChangeWhenThresholdExceeded(error);
}
private void handleStateChangeWhenThresholdExceeded(Throwable error) {
......
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);
}
}
通过counter计算出单位统计时间的错误量,判断是否超过锚点,调用transformToOpen -> fromCloseToOpen
protected boolean fromCloseToOpen(double snapshotValue) {
State prev = State.CLOSED;
if (currentState.compareAndSet(prev, State.OPEN)) {
updateNextRetryTimestamp();
notifyObservers(prev, State.OPEN, snapshotValue);
return true;
}
return false;
}
保证线程安全,通过CAS (compareAndSet) 将状态改为OPEN。
这是本资源对应的熔断器状态变为OPEN,之后流量进来时
//DegradeSlot.java
void performChecking(Context context, ResourceWrapper r) throws BlockException {
List<CircuitBreaker> circuitBreakers = DegradeRuleManager.getCircuitBreakers(r.getName());
if (circuitBreakers == null || circuitBreakers.isEmpty()) {
return;
}
for (CircuitBreaker cb : circuitBreakers) {
//调用 breaker的tryPass方法
if (!cb.tryPass(context)) {
throw new DegradeException(cb.getRule().getLimitApp(), cb.getRule());
}
}
}
//AbstractCircuitBreaker.java
@Override
public boolean tryPass(Context context) {
// Template implementation.
if (currentState.get() == State.CLOSED) {
return true;
}
if (currentState.get() == State.OPEN) {
// For half-open state we allow a request for probing.
//状态为Open,会进来这里
return retryTimeoutArrived() && fromOpenToHalfOpen(context);
}
return false;
}
会先检查,断开时间是否已到,如果到了,会切换到半开状态,然后返回false,不通过并抛出DegradeException
之后的第一个请求进来如果没有抛出异常,则将状态从halfOpen 改为Close,进行放行状态,这个过程在 entry.exit 时调用handleStateChangeWhenThresholdExceeded
时完成
Sentinel 源码分析- 熔断降级原理分析的更多相关文章
- 老李推荐:第6章8节《MonkeyRunner源码剖析》Monkey原理分析-事件源-事件源概览-小结
老李推荐:第6章8节<MonkeyRunner源码剖析>Monkey原理分析-事件源-事件源概览-小结 本章我们重点围绕处理网络过来的命令的MonkeySourceNetwork这个事 ...
- 老李推荐:第6章7节《MonkeyRunner源码剖析》Monkey原理分析-事件源-事件源概览-注入按键事件实例
老李推荐:第6章7节<MonkeyRunner源码剖析>Monkey原理分析-事件源-事件源概览-注入按键事件实例 poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜 ...
- 老李推荐:第6章6节《MonkeyRunner源码剖析》Monkey原理分析-事件源-事件源概览-命令队列
老李推荐:第6章6节<MonkeyRunner源码剖析>Monkey原理分析-事件源-事件源概览-命令队列 事件源在获得字串命令并把它翻译成对应的MonkeyEvent事件后,会把这些 ...
- 老李推荐:第6章4节《MonkeyRunner源码剖析》Monkey原理分析-事件源-事件源概览-翻译命令字串
老李推荐:第6章4节<MonkeyRunner源码剖析>Monkey原理分析-事件源-事件源概览-翻译命令字串 poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自 ...
- 老李推荐:第6章5节《MonkeyRunner源码剖析》Monkey原理分析-事件源-事件源概览-事件
老李推荐:第6章5节<MonkeyRunner源码剖析>Monkey原理分析-事件源-事件源概览-事件 从网络过来的命令字串需要解析翻译出来,有些命令会在翻译好后直接执行然后返回,但有 ...
- 老李推荐:第6章3节《MonkeyRunner源码剖析》Monkey原理分析-事件源-事件源概览-命令翻译类
老李推荐:第6章3节<MonkeyRunner源码剖析>Monkey原理分析-事件源-事件源概览-命令翻译类 每个来自网络的字串命令都需要进行解析执行,只是有些是在解析的过程中直接执行 ...
- 老李推荐:第6章2节《MonkeyRunner源码剖析》Monkey原理分析-事件源-事件源概览-获取命令字串
老李推荐:第6章2节<MonkeyRunner源码剖析>Monkey原理分析-事件源-事件源概览-获取命令字串 从上一节的描述可以知道,MonkeyRunner发送给Monkey的命令 ...
- 老李推荐:第5章7节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 循环获取并执行事件 - runMonkeyCycles
老李推荐:第5章7节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 循环获取并执行事件 - runMonkeyCycles poptest是国内唯一一家培养测试开 ...
- 老李推荐:第5章6节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 初始化事件源
老李推荐:第5章6节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 初始化事件源 poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试 ...
随机推荐
- MySQL-2-DQL
DQL:数据查询语言 SQLyog中格式化某段语句片段:CTRL+F12 基础查询 语法: select 查询列表 from 表名: 特点: ① 查询列表可以是:表中的字段.常量值.表达式.函数 ② ...
- nginx启动失败/报错(bind() to 0.0.0.0:80 failed (10013: An attempt was made to access a socket...permissions) nginx启动失败
出现这个问题是因为80端口被占用了 1.cmd输入命令netstat -aon|findstr "80" 2..查看80端口 16356对应的任务 输入命令 tasklist|fi ...
- 记一次 .NET 某工控数据采集平台 线程数 爆高分析
一:背景 1. 讲故事 前几天有位朋友在 B站 加到我,说他的程序出现了 线程数 爆高的问题,让我帮忙看一下怎么回事,截图如下: 说来也奇怪,这些天碰到了好几起关于线程数无缘无故的爆高,不过那几个问题 ...
- 动画 ---Animejs 简单使用与源码解析
Anime是什么 Anime有什么用 Anime是作何做的 requireAnimationFrame() engine(){ // 处理让多个帧运动起来 play() step()} ani ...
- C语言-typedef和#define
1.关键字 typedef 为一个数据类型起一个新的别名,typedef 的用法一般为: typedef OldName NewName; 需要强调的是,typedef 是赋予现有类型一个新的名字,而 ...
- Calendar类介绍_获取对象的方式和Calendar类的常用成员方法
java.util.Calendar类:日历类 Calendar类是一个抽象类,里边提供了很多操作日历字段的方法(YEAR.MONTH.DAY_OF_MONTH.HOUR ) Calendar类无法直 ...
- Java 插入公式到PPT幻灯片
PowerPoint幻灯片中可插入公式,用于在幻灯片放映时演示相关内容的论证.推算的依据,能有效地为演讲者提供论述的数据支撑.通过后端程序代码,我们可借助特定的工具来实现在幻灯片中的插入公式,本文,将 ...
- 什么是好的 API 设计?【eolink翻译】
对于试图完善其 API 策略的团队来说,良好的 API 设计是一个经常出现的话题. API 设计的重要性相信不需要赘述,精心设计的 API 的好处包括:更好开发人员体验.更快的文档编制以及更高的 AP ...
- Docker 安全及日志管理
Docker 安全及日志管理 容器的安全性问题的根源在于容器和宿主机共享内核. 容器里的应用导致Linux内核崩溃,那么整个系统可能都会崩溃. 虚拟机并没有与主机共享内核,虚拟机崩溃一般不会导致宿主机 ...
- .net 温故知新:【6】Linq是什么
1.什么是Linq 关于什么是Linq 我们先看看这段代码. List<int> list = new List<int> { 1, 1, 2, 2, 3, 3, 3, 5, ...