1.工作主流程
介绍sentinel工作主要流程前我们先思考一个问题:如果是你来实现一个限流框架你会怎么做?
我先讲下我的一些思考,主要从这么几个方面去做:
-
- 确定好限流算法,如计数器、漏桶、令牌桶等算法
- 实现限流需要哪些条件?装配配置的限流接口限流阈值限流策略等,采集调用信息、触发限流规则
- 框架拓展性考虑引入spi机制
- 用什么设计模式去实现,方便拓展?
接下来我们带着我们的思考与实现思路,看下Sentinel的主要工作原理,以及他的设计实现。
Sentinel整体架构设计图:
sentinel 通过一个个插槽来实现限流功能,采用职责链设计模式去组装插槽,每个插槽的功能职责都不一样,Sentinel提供的插槽如下:
- NodeSelectorSlot 负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级;
- ClusterBuilderSlot 则用于存储资源的统计信息以及调用者信息,例如该资源的 RT, QPS, thread count 等等,这些信息将用作为多维度限流,降级的依据;
- StatisticSlot 则用于记录、统计不同纬度的 runtime 指标监控信息;
- FlowSlot 则用于根据预设的限流规则以及前面 slot 统计的状态,来进行流量控制;
- AuthoritySlot 则根据配置的黑白名单和调用来源信息,来做黑白名单控制;
- DegradeSlot 则通过统计信息以及预设的规则,来做熔断降级;
- SystemSlot 则通过系统的状态,例如 load1 等,来控制总的入口流量;
sentinel内部实现了SPI机制,提供了拓展功能,所以我们可以去自定义实现插槽并添加到Sentinel工作主流程中,如下图所示:
2.工作流程源码解析
Sentinel-dubbo-adapter适配dubbo做限流降级,它的使用示例代码
2.1示例
限流:
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// Get origin caller.
String origin = DubboOriginParserRegistry.getDubboOriginParser().parse(invoker, invocation);
if (null == origin) {
origin = "";
}
Entry interfaceEntry = null;
Entry methodEntry = null;
try {
String resourceName = getResourceName(invoker, invocation, DubboConfig.getDubboProviderPrefix());
String interfaceName = invoker.getInterface().getName();
ContextUtil.enter(resourceName, origin);
interfaceEntry = SphU.entry(interfaceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN);
methodEntry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_RPC,
EntryType.IN, invocation.getArguments());
Result result = invoker.invoke(invocation);
if (result.hasException()) {
Throwable e = result.getException();
// Record common exception.
Tracer.traceEntry(e, interfaceEntry);
Tracer.traceEntry(e, methodEntry);
}
return result;
} catch (BlockException e) {
return DubboFallbackRegistry.getProviderFallback().handle(invoker, invocation, e);
} catch (RpcException e) {
Tracer.traceEntry(e, interfaceEntry);
Tracer.traceEntry(e, methodEntry);
throw e;
} finally {
if (methodEntry != null) {
methodEntry.exit(1, invocation.getArguments());
}
if (interfaceEntry != null) {
interfaceEntry.exit();
}
ContextUtil.exit();
}
}
熔断:
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
Entry interfaceEntry = null;
Entry methodEntry = null;
try {
String resourceName = getResourceName(invoker, invocation, DubboConfig.getDubboConsumerPrefix());
interfaceEntry = SphU.entry(invoker.getInterface().getName(), ResourceTypeConstants.COMMON_RPC,
EntryType.OUT);
methodEntry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT, invocation.getArguments());
Result result = invoker.invoke(invocation);
if (result.hasException()) {
Throwable e = result.getException();
// Record common exception.
Tracer.traceEntry(e, interfaceEntry);
Tracer.traceEntry(e, methodEntry);
}
return result;
} catch (BlockException e) {
return DubboFallbackRegistry.getConsumerFallback().handle(invoker, invocation, e);
} catch (RpcException e) {
Tracer.traceEntry(e, interfaceEntry);
Tracer.traceEntry(e, methodEntry);
throw e;
} finally {
if (methodEntry != null) {
methodEntry.exit(1, invocation.getArguments());
}
if (interfaceEntry != null) {
interfaceEntry.exit();
}
}
}
从以上代码可看出来,Sentinel限流熔断功能的核心API主要有:
- ContextUtil.enter(resourceName, origin);
- SphU.entry();
- interfaceEn