Sentinel框架开发的巴西slots游戏源码搭建架构解析

一、概述

前面介绍过Sentinel核心框架就是通过插槽链一层层的调用,每个插槽的功能如下:

  • NodeSelectorSlot 负责收集资源的路径,并将这些资源的调用路径,以树状结-构存储起来,用于根据调用路径来限流降级。
  • ClusterBuilderSlot 则用于存储资源的统计信息以及调用者信息,例如该资源的 RT, QPS, thread count 等等,这些信息将用作为多维度限流,降级的依据。
  • StatisticSlot 则用于记录、统计不同纬度的 runtime 指标监控信息。
  • LogSlot 则用于记录blockException信息的日志信息,会写入的日志文件中。
  • ParamFlowSlot 则用于根据热点参数进行限流控制的。
  • SystemSlot 则通过系统的状态,例如 load1 等,来控制总的入口流量。
  • AuthoritySlot 则根据配置的黑白名单和调用来源信息,来做黑白名单控制。
  • FlowSlot 则用于根据预设的限流规则以及前面 slot 统计的状态,来进行流量控制。
  • DegradeSlot 则通过统计信息以及预设的规则,来做熔断降级。

这边文章会先介绍NodeSelectorSlot和ClusterBuilderSlot功能

二、节点Node

我们知道在进行资源的限流降级中,需要拿到Entry,Entry就像是一个凭证,拿到这个凭证,代码才能继续走下去。Entry又被封装到上下文对象Context中。在Context中还有一个entranceNode节点,代表这个资源调用树一个入口。Entry中又有curNode、originNode这两个属性。curNode保存了这一次调用统计信息,riginNode保存了在这个上下文中,所有的调用的统计信息和。

有这么多Node,那他们之间的关系是怎样的,如图:

slots游戏开发tg@yuantou2048部署架构

Node

  • Node是一个接口,有很多各种指标的方法,Sentinel就用通过这些指标进行限流降级的。
  • StatisticNode:统计实时统计指标的Node。有两个子类DefaultNode和ClusterNode。
  • EntranceNode:EntranceNode是每个上下文的入口,该节点是挂在root下的,是全局唯一的,每一个context都会对应一个entranceNode。
  • DefaultNode:DefaultNode是记录当前调用实时数据的,在同一个上下文中不同资源关联着不同的DefaultNode。在同一个上下文中,对不同的资源调用,DefaultNode会有子childNode生成。
  • ClusterNode:全局的统计数据,包括 rt, thread count, qps等,相同的资源关DefaultNode联着统一个ClusterNode,无论它在哪个上下文中。

三、NodeSelectorSlot

在Sentinel之Entry构建源码解析 这篇文章介绍过,Entry可以理解Context这棵树的树干,那么NodeSelectorSlot这个插槽可以理解为正式构建entry叶子而形成的,下面具体分析。

//注意这里的可以使context的name
private volatile Map<String, DefaultNode> map = new HashMap<String, DefaultNode>(10);
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
throws Throwable {
DefaultNode node = map.get(context.getName());
if (node == null) {
synchronized (this) {
node = map.get(context.getName());
if (node == null) {
node = Env.nodeBuilder.buildTreeNode(resourceWrapper, null);
HashMap<String, DefaultNode> cacheMap = new HashMap<String, DefaultNode>(map.size());
cacheMap.putAll(map);
cacheMap.put(context.getName(), node);
map = cacheMap;
}
// Build invocation tree
((DefaultNode)context.getLastNode()).addChild(node);
}
}
context.setCurNode(node);
}

分析

  • 1、从map中获取改context下的defaultNode,如果defaultNode不存在,到2,反之直接到4。
  • 2、双重锁校验defaultNode存不存,若不存在,则创建一个defaultNode,并重新构造map,并添加到map中,然后到3。
  • 3、获取context的当前调用Node的LastNode,并把该node添加到子Node中。
  • 4、重新设置Context的curEntry的curNode。

看图分析

在一个Context中第一次进入时,在第三步,调用Context的getLastNode方法。

//在Context中
public Node getLastNode() {
//curEntry.getLastNode() 调用CtEntry的方法getLastNode
if (curEntry != null && curEntry.getLastNode() != null) {
return curEntry.getLastNode();
} else {
return entranceNode;
}
}
在CtEntry中
@Override
public Node getLastNode() {
return parent == null ? null : parent.getCurNode();
}

上篇文章分析过Entry的构建过程,第一次调用时:

Entry

可以发现curEntry != null条件满足,但是parent是null。所以,lastNode第一次的值就是context的entranceNode,然后将node添加到entranceNode中,并设置curEntry的curNode节点。然后Context内存结构变成如下。

defaultNode

接着又一个请求进入,若资源不同,在生成一个新的Entry后(上一篇分析过)。

这个时候再次调用context.getLastNode()将会返回parent.getCurNode(),把这个节点放入到lastNode的子节点中,再把node设置给context的curEntry。最后Context内存结构变成如下。

defaultNode

这里假设的是资源名不同的情况,若是资源相同的话,则context.getLastNode()).addChild(node);逻辑就不会执行,只会把当前node设置为context的curEntry中。这时的内存结构如图。

defaultNode

上面分析了在NodeSelectorSlot中CurEntry叶子构建的过程,在一次构建中会设置entranceNode的childNode。

资源路径收集

经过上述分析可以发现,NodeSelectorSlot主要负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级。

ContextUtil.enter("entrance1", "appA");
Entry nodeA = SphU.entry("nodeA");
if (nodeA != null) {
nodeA.exit();
}
ContextUtil.exit();

上述代码通过 ContextUtil.enter() 创建了一个名为 entrance1 的上下文,同时指定调用发起者为 appA;接着通过 SphU.entry()请求一个 token,如果该方法顺利执行没有抛 BlockException,表明 token 请求成功。

以上代码将在内存中生成以下结构:

machine-root
/
/
EntranceNode1
/
/
DefaultNode(nodeA
注意:每个 DefaultNode 由资源 ID 和输入名称来标识。换句话说,一个资源 ID 可以有多个不同入口的 DefaultNode。
ContextUtil.enter("entrance1", "appA");
Entry nodeA = SphU.entry("nodeA");
if (nodeA != null) {
nodeA.exit();
}
ContextUtil.exit();
ContextUtil.enter("entrance2", "appA");
nodeA = SphU.entry("nodeA");
if (nodeA != null) {
nodeA.exit();
}
ContextUtil.exit();
以上代码将在内存中生成以下结构:
machine-root
/ \
/ \
Entrance1 Entrance2
/ \
/ \
DefaultNode(nodeA) DefaultNode(nodeA)
| |
+- - - - - - - - - - +- - - - - - -> ClusterNode(nodeA);

可以发现同一个nodeA资源共用一个ClusterNode,而不管它在哪一个上线文中。接下面讲解ClusterBuilderSlot,来看ClusterNode构造。

四、ClusterBuilderSlot

ClusterBuilderSlot插槽用于构建资源的 ClusterNode 以及调用来源节点。ClusterNode 保持资源运行统计信息(响应时间、QPS、block 数目、线程数、异常数等)以及原始调用者统计信息列表。来源调用者的名字由 Context.enter(contextName,origin) 中的 origin 标记。

看源码:

public class ClusterBuilderSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
private static volatile Map<ResourceWrapper, ClusterNode> clusterNodeMap
= new HashMap<ResourceWrapper, ClusterNode>();
private static final Object lock = new Object();
private ClusterNode clusterNode = null;
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
boolean prioritized, Object... args)
throws Throwable {
if (clusterNode == null) {
synchronized (lock) {
if (clusterNode == null) {
// Create the cluster node.
clusterNode = Env.nodeBuilder.buildClusterNode();
HashMap<ResourceWrapper, ClusterNode> newMap = new HashMap<ResourceWrapper, ClusterNode>(16);
newMap.putAll(clusterNodeMap);
newMap.put(node.getId(), clusterNode);
clusterNodeMap = newMap;
}
}
}
node.setClusterNode(clusterNode);
/*
* if context origin is set, we should get or create a new {@link Node} of
* the specific origin.
*/
if (!"".equals(context.getOrigin())) {
Node originNode = node.getClusterNode().getOrCreateOriginNode(context.getOrigin());
context.getCurEntry().setOriginNode(originNode);
}
fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
//以下代码省略
}

细心地人可以发现ClusterBuilderSlot集成的AbstractLinkedProcessorSlot的泛型对象由Object变为DefaultNode了,这是因为DefaultNode已经在NodeSelectorSlot插槽中构建好了。

来看ClusterBuilderSlot插槽的entry方法做了哪些事。

  • 1、判断clusterNode是否为空,若为空,到2,反之到3。
  • 2、创建一个clusterNode节点,并添加到clusterNodeMap中,注意这里的map的可以使ResourceWrapper,用以表示不区分在哪个上下文中,到3。
  • 3、设置clusterNode到defaultNode中,到4。
  • 4、如果context的origin不为空,则把originNode设置到Context当前cutEntry的originNode中。这个originNode用于后续根据调用源进行限流或资源保护。

五、我的小结

1、本文是插槽分析的第一篇,介绍的NodeSelectorSlot和ClusterBuilderSlot的作用。

2、NodeSelectorSlot用来构建Context的curEntry的叶子节点,不同的资源id在不同的上下文中有不同的入口,并且对应不同的defaultNode,但同一个资源对应同一个clusterNode。

3、ClusterBuilderSlot设置了defaultNode的clusterNode,并设置了Entry的originNode。clusterNode保存clusterNodeMap中,可以发现系统运行的时间越长,这个map就越稳定。

4、NodeSelectorSlot和ClusterBuilderSlot是整个插槽链中的前两个插槽,这个两个插槽完成Context数据的封装和对资源调用链Node包装,以便对后续数据的收集和资源的保护限流。返回搜狐,查看更多

责任编辑:

平台声明:该文观点仅代表作者本人,搜狐号系信息发布平台,搜狐仅提供信息存储空间服务。
阅读 ()
大家都在看
推荐阅读