NodeSelectorSlot
NodeSelectorSlot is responsible for creating a DefaultNode for the first access to resources and maintaining Context.curNode and call tree,
If SphU#entry is called more than once on a call link, the center generated by each call will eventually become a two-way linked list and stored in the Context.
NodeSelectorSlot is placed in the first position of the ProcessorSlotChain chain list because subsequent processorslots need to rely on this ProcessorSlot.
public class NodeSelectorSlot extends AbstractLinkedProcessorSlot<Object> { // Context of name -> Resource DefaultNode private volatile Map<String, DefaultNode> map = new HashMap<>(10); // Entrance method @Override public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args) throws Throwable { // use Context Name of as key Of cache resources DefaultNode DefaultNode node = map.get(context.getName()); if (node == null) { synchronized (this) { node = map.get(context.getName()); if (node == null) { // Create for resource DefaultNode node = new DefaultNode(resourceWrapper, null); // replace map HashMap<String, DefaultNode> cacheMap = new HashMap<>(map.size()); cacheMap.putAll(map); cacheMap.put(context.getName(), node); map = cacheMap; // Binding call tree ((DefaultNode) context.getLastNode()).addChild(node); } } } // replace Context of curNode For current DefaultNode context.setCurNode(node); fireEntry(context, resourceWrapper, node, count, prioritized, args); } // The export method does nothing @Override public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { fireExit(context, resourceWrapper, count, args); } }
Function of NodeSelectorSlot#map:
A resource corresponds to a slotchain, and a DefaultNode created by different context s is recorded under a slotchain
How many defaultnodes Sentinel will create for the same resource ID depends on how many call chains use them as entry resources. The straight white point is that how many defaultnodes exist in the same resource depends on how many different values Context.name has. This is why a resource may have multiple defaultnodes.
for instance,
For the same payment interface, we need to use Spring MVC to expose to front-end access, and may also use Dubbo to expose to other internal service calls. Sentinel's Web MVC adapter creates a Context named "sentinel_spring_web_context" at the call link entry, which is different from the Context created by sentinel's Dubbo adapter calling the ContextUtil#enter method. In this case, we can only limit the flow of Spring MVC, that is, limit the QPS of interface calls initiated by the front end and the number of threads occupied in parallel.
The default context used for spring webmvc interception is implemented by the class com.alibaba.csp.sentinel.adapter.spring.webmvc.AbstractSentinelInterceptor. The default value is sentinel_spring_web_context .
context+resource confirm the unique deaultnode.
One context corresponds to one EntranceNode,
A resource corresponds to a ClusterNode and a ProcessorSlotChain, ,
Each ProcessorSlotChain contains a Map that stores the relationship between context and DefaultNode under this resource.
A Entry call chain is stored under the same context. Each Entry executes its acceptance and calls entry.exit(1) to exit this Node and return to Parent CEntry.
Each StatisticsNode also has a Map, which contains the origin information of the Node, which is used to make differential statistics for nodes from different sources.
node relation table
ClusterNode constructor: ClusterBuilderSlot
Background of ClusterNode
In the ProcessorSlotChain of a resource, NodeSelectorSlot is responsible for creating a DefaultNode for the resource, which can only be used by the Context with the same name. Therefore, there may be multiple defaultnodes in a resource. If you want to obtain the total QPS of a resource, you must traverse these defaultnodes. For performance reasons, Sentinel will create a globally unique ClusterNode for each resource, which is used to count the global parallel threads, QPS, total exceptions and other indicator data of the resource.
public class ClusterBuilderSlot extends AbstractLinkedProcessorSlot<DefaultNode> { // resources -> ClusterNode private static volatile Map<ResourceWrapper, ClusterNode> clusterNodeMap = new HashMap<>(); private static final Object lock = new Object(); // Non static, one resource corresponds to one ProcessorSlotChain,So one resource shares one ClusterNode private volatile 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) { // establish ClusterNode clusterNode = new ClusterNode(resourceWrapper.getName(), resourceWrapper.getResourceType()); // Add to cache HashMap<ResourceWrapper, ClusterNode> newMap = new HashMap<>(Math.max(clusterNodeMap.size(), 16)); newMap.putAll(clusterNodeMap); newMap.put(node.getId(), clusterNode); clusterNodeMap = newMap; } } } // node by NodeSelectorSlot Passed on DefaultNode node.setClusterNode(clusterNode); // If origin If it is not empty, create one for the remote StatisticNode if (!"".equals(context.getOrigin())) { Node originNode = node.getClusterNode().getOrCreateOriginNode(context.getOrigin()); context.getCurEntry().setOriginNode(originNode); } fireEntry(context, resourceWrapper, node, count, prioritized, args); } @Override public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { fireExit(context, resourceWrapper, count, args); } } // ClusterNode#getOrCreateOriginNode //origin Indicates the name of the source application public Node getOrCreateOriginNode(String origin) { StatisticNode statisticNode = originCountMap.get(origin); if (statisticNode == null) { lock.lock(); try { statisticNode = originCountMap.get(origin); if (statisticNode == null) { // The node is absent, create a new node for the origin. statisticNode = new StatisticNode(); HashMap<String, StatisticNode> newMap = new HashMap<>(originCountMap.size() + 1); newMap.putAll(originCountMap); newMap.put(origin, statisticNode); originCountMap = newMap; } } finally { lock.unlock(); } } return statisticNode; }
remarks:
ClusterNode has a Map type field to cache the mapping between origin and StatisticNode. If the upstream service passes the origin field to the interface calling the current service, ClusterBuilderSlot will create a StatisticNode for ClusterNode to count the indicator data of the current resource called by the remote service.
For example, when origin represents the name of the source application, the corresponding StatisticNode counts the indicator data for the call source, which can be used to see which service accesses the interface most frequently, so as to limit the flow according to the call source.
Reference site: http://learn.lianglianglee.com/%E4%B8%93%E6%A0%8F/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3%20Sentinel%EF%BC%88%E5%AE%8C%EF%BC%89/08%20%E8%B5%84%E6%BA%90%E6%8C%87%E6%A0%87%E6%95%B0%E6%8D%AE%E7%BB%9F%E8%AE%A1%E7%9A%84%E5%AE%9E%E7%8E%B0%E5%85%A8%E8%A7%A3%E6%9E%90%EF%BC%88%E4%B8%8A%EF%BC%89.md