该篇是分析Sentinel
原理的第一篇文章。由于接触该中间件的时间较短,可能会出现一些理解偏差。希望大家积极交流改正。文章主要内容主要是阅读了一些大神的文章,并加上自己的理解写的。
主要参考的文章是在Sentinel
的github中找到的,具体地址如下: 参考文章
Sentinel的主要功能就是流量控制和熔断降级。具体概念在之前写的有关dubbo的限流和熔断中已介绍,这里就不再赘述了。
先简单举一个如何使用的例子。
public static void main(String[] args) {
try {
Context context=ContextUtil.enter("context1");
Entry entry=SphU.entry("HelloWorld");
entry.exit();
ContextUtil.exit();
} catch (BlockException ex) {
// 处理被流控的逻辑
System.out.println("blocked!");
}catch (Exception e){
e.printStackTrace();
}
}
try (Entry entry = SphU.entry("HelloWorld")) {
// Your business logic here.
System.out.println("hello world");
} catch (BlockException e) {
// Handle rejected request.
e.printStackTrace();
}
上面是两个典型的流量控制例子。读者会发现有一处很明显的不同,即Context
类,它用来表示一个调用的上下文,实际不加也影响,具体的在后面分析。
例子中还有一个重要的类Entry
,里面的参数HelloWorld
可以看做一个资源名,在具体使用之前,我们需要自己定义一些规则。 可以使用下面的方法定义一个关于流量控制的规则:
private void initFlowQpsRule() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule(resourceName);
// set limit qps to 20
rule.setCount(20);
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setLimitApp("default");
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
当调用SphU.entry("HelloWorld")
时,如果没有报异常,说明可以执行自己的业务代码;如果报错,说明当前不符合条件,拒绝执行后面的业务代码。
几个核心概念
Resource
资源是Sentinel中的一个核心概念,它可以是应用程序提供的服务,也可以是应用程序调用的其他服务,也可以是一段代码。当某个服务的请求非常多,经常被陡增的流量冲垮,造成性能降低或者不可用,这时我们可以定义一个Sentinel的资源,通过该资源对请求进行调整,进行流量控制和服务降级。
在Sentinel中表示具体资源的类是ResourceWrapper
。
public abstract class ResourceWrapper {
# 资源名
protected final String name;
# 是入站还是出站
protected final EntryType entryType;
# 资源类型
protected final int resourceType;
Entry
entry是sentinel中用来表示是否通过限流的一个凭证,如果能正常返回,则说明你可以访问被sentinel保护的后方服务,否则sentinel会抛出一个BlockException。另外,它保存了本次执行entry()
方法的一些基本信息。每一次资源调用都会创建一个Entry
。
public abstract class Entry implements AutoCloseable {
# 当前Entry的创建时间,主要用来后期计算rt
private long createTime;
# 当前Entry所关联的node,该node主要是记录了当前context下该资源的统计信息
private Node curNode;
# 当前Entry的调用来源,通常是调用方的应用名称
private Node originNode;
private Throwable error;
# 当前Entry所关联的资源
protected ResourceWrapper resourceWrapper;
class CtEntry extends Entry {
protected Entry parent = null;
protected Entry child = null;
protected ProcessorSlot<Object> chain;
protected Context context;
可能这样介绍有些抽象,我借鉴一个别人的例子,很形象。
从图中我们可以看出有一个
user-center
--> getUserInfo
--> getOrderInfo
的调用链路。根据代码,我们知道user-center
是一个上下文名称,getUserInfo
和getOrderInfo
是两个资源名称。
上面例子中在一个Entry中又调用了一个Entry,存在链路,当在一个上下文中多次调用Sphu.entry()
方法,就会创建一个调用树,这个树的节点之间是通过parent和child关系维持的。
Node
node中保存了资源的实时统计数据,例如:passQps,blockQps,rt等实时数据。
node是一个接口,它有一个实现类StatisticNode
,但是StatisticNode
本身也有两个子类,一个是DefaultNode
,另一个是ClusterNode
,DefaultNode
又有一个子类叫EntranceNode
。
EntranceNode
是每个上下文的入口,该节点是直接挂在root下,是全局唯一的,每一个context都有一个对应的entranceNode
。DefaultNode
是记录当前实时数据的,每个DefaultNode
都关联着一个资源和ClusterNode
。有着相同资源的defaultNode,它们关联着同一个clusterNode。
public interface Node extends OccupySupport, DebugSupport {
long totalRequest();
long totalPass();
long totalSuccess();
public class DefaultNode extends StatisticNode {
private ResourceWrapper id;
private volatile Set<Node> childList = new HashSet<>();
private ClusterNode clusterNode;
Context
public class Context {
//上下文名称
private final String name;
//当前调用链的入口节点
private DefaultNode entranceNode;
//调用链中当前正在处理的entry
private Entry curEntry;
//此上下文的来源(通常表示不同的调用方,例如服务使用者名称或来源IP)
private String origin = "";
Context
代表调用链路上下文,贯穿一次调用链路中的所有Entry
。
每次调用SphU.entry()
都需要在一个context中执行,如果没有当前执行时还没有context,那么框架会使用默认的context,即sentinel_default_context
。
Context
是保存在ThreadLocal
中的,每次执行的时候会优先到ThreadLocal中获取。如果Context为null才会再次去创建一个context。
当Entry
执行exit方法时,当当前的entry的parent为null时,说明当前entry是最上层的节点,该上下文的entry已执行完,所以在此时需要将context设置为null,从ThreadLocal中清除。
以上就是比较重要的实体类介绍。
网友评论