Sentine 源码分析之--NodeSelectorSlot、ClusterBuilderSlot、StatisticSlot

前言:

前面我们对 Sentinel 有了一个比较系统的认知,本篇我们正式开始分析 Sentinel 的源码,我们知道 Sentinel 使用了责任链模式,根据 Sentinel 官方提供的流程图,我们知道 NodeSelectorSlot 是整个链路的入口,下面我们开始分析 Sentinel 源码。

Sentinel 系列文章传送门:

Sentinel 初步认识及使用

Sentinel 核心概念和工作流程详解

Spring Cloud 整合 Nacos、Sentinel、OpenFigen 实战【微服务熔断降级实战】

Sentinel 源码分析入门【Entry、Chain、Context】

在这里插入图片描述

NodeSelectorSlot#entry 方法源码解析

NodeSelectorSlot#entry 方法会根据资源名称获取 DefaultNode 链路节点,DefaultNode 用于统计调用链路上某个资源的数据,维持树状结构,然后执行下一个 Slot,也就是 ClusterBuilderSlot,详细拆分的话 NodeSelectorSlot 做了一下几件事:

  • 为当前资源创建 DefaultNode。
  • 将 DefaultNode 放入缓存中,key 是 contextName,这样不同链路入口的请求,将会创建多个 DefaultNode,相同链路则只有一个DefaultNode。
  • 将当前资源的 DefaultNode 设置为上一个资源的 ChildNode,构造树。
  • 将当前资源的 DefaultNode 设置为 Context 中的 curNode,也就是当前节点。
//com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot#entry
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
	throws Throwable {
	
	//获取当前资源的  DefaultNode
	DefaultNode node = map.get(context.getName());
	//DefaultNode 为空判断
	if (node == null) {
		//node 为空
		//synchronized 加锁
		synchronized (this) {
			//再次获取 node
			node = map.get(context.getName());
			//再次为空判断
			if (node == null) {
				//创建一个 DefaultNode
				node = new DefaultNode(resourceWrapper, null);
				//创建 map 加入缓存
				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);
			}

		}
	}
	//设置当前 node
	context.setCurNode(node);
	//执行下一个 slot
	fireEntry(context, resourceWrapper, node, count, prioritized, args);
}

AbstractLinkedProcessorSlot#fireEntry 方法源码解析

AbstractLinkedProcessorSlot#fireEntry 是一个通用方法,每个 Slot 执行完毕后都会调用这个方法,去判断是否有下一个 Slot。

//com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot#fireEntry
@Override
public void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
	throws Throwable {
	//next 为空判断
	if (next != null) {
		//不为空 继续
		next.transformEntry(context, resourceWrapper, obj, count, prioritized, args);
	}
}

@SuppressWarnings("unchecked")
//com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot#transformEntry
void transformEntry(Context context, ResourceWrapper resourceWrapper, Object o, int count, boolean prioritized, Object... args)
	throws Throwable {
	T t = (T)o;
	entry(context, resourceWrapper, t, count, prioritized, args);
}


ClusterBuilderSlot#entry 方法源码分析

ClusterBuilderSlot#entry 的逻辑比较简单,用于构建资源的 ClusterNode 以及调用来源节点,ClusterNode 保持某个资源运行统计信息(响应时间、QPS、block 数目、线程数、异常数等)以及调用来源统计信息列表,接下来就是 StatisticSlot。

@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
				  boolean prioritized, Object... args)
	throws Throwable {
	//clusterNode 为空判断
	if (clusterNode == null) {
		//synchronized 锁
		synchronized (lock) {
			//clusterNode 为空判断 double check
			if (clusterNode == null) {
				// Create the cluster node.
				//创建 clusterNode
				clusterNode = new ClusterNode(resourceWrapper.getName(), resourceWrapper.getResourceType());
				//加入缓存
				HashMap<ResourceWrapper, ClusterNode> newMap = new HashMap<>(Math.max(clusterNodeMap.size(), 16));
				newMap.putAll(clusterNodeMap);
				newMap.put(node.getId(), clusterNode);

				clusterNodeMap = newMap;
			}
		}
	}
	//设置 clusterNode
	node.setClusterNode(clusterNode);

	/*
	 * if context origin is set, we should get or create a new {@link Node} of
	 * the specific origin.
	 */
	//记录请求来源 origin
	if (!"".equals(context.getOrigin())) {
		//获取 origin
		Node originNode = node.getClusterNode().getOrCreateOriginNode(context.getOrigin());
		//设置 origin
		context.getCurEntry().setOriginNode(originNode);
	}
	//继续下一个 slot
	fireEntry(context, resourceWrapper, node, count, prioritized, args);
}

StatisticSlot#entry 方法源码解析

StatisticSlot 是 Sentinel 的核心功能插槽之一,用于统计实时的调用数据,我们发现这个 Slot 不太一样,她是一上来就执行 fireEntry 方法,进入下一个 Slot 的逻辑,这样设计的原因是由它的功能决定的,它的作用是统计实时数据,必须要后面得限流、降级 Slot 执行完,才能够拿到结果数据,如果后续的 Slot 都执行通过了,正常的执行完 fireEntry,这个时候需要对一些 Node 进行指标的增加,例如线程数 、QPS ,如果限流、降级 Slot 没有通过就会抛出异常,这个时候就不会再走 fireEntry 下面的代码,而是走catch里面的代码,针对不同的异常进行了不同的处理,我们按条理梳理如下:

  • 执行 fireEntry 方法,执行后面的后面得限流、降级 Slot。
  • 如果限流、降级 Slot 执行通过,会区分 Node 节点类型,对通过的进行指标增加。
  • 如果限流、降级 Slot 执行没有通过,会抛出异常,代码会直接进入 catch 代码块。
  • 如果是优先级等待异常,不会增加指标的失败数量,但会区分 Node 节点类型,对通过进行指标增加。
  • 如果是阻塞没有通过异常,会区分 Node 节点类型,对阻塞指标进行增加。
  • 如果是 Throwable 异常,会设置错误信息到当前 Entry 中。
//com.alibaba.csp.sentinel.slots.statistic.StatisticSlot#entry
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
				  boolean prioritized, Object... args) throws Throwable {
	try {
		// Do some checking.
		//放行到下一个 slot 做限流 降级 等规则判断
		fireEntry(context, resourceWrapper, node, count, prioritized, args);

		// Request passed, add thread count and pass count.
		//请求已通过 线程数+1 用做线程隔离
		node.increaseThreadNum();
		//请求通过 计数器+1 用做限流
		node.addPassRequest(count);
		//请求来源节点判断
		if (context.getCurEntry().getOriginNode() != null) {
			// Add count for origin node.
			//来源节点不为空 来源节点的 线程数 和  计数器 也+1
			context.getCurEntry().getOriginNode().increaseThreadNum();
			context.getCurEntry().getOriginNode().addPassRequest(count);
		}
		//是否是入口资源类型
		if (resourceWrapper.getEntryType() == EntryType.IN) {
			// Add count for global inbound entry node for global statistics.
			//如果是入口资源类型 全局线程数 和 计数器 也要+1
			Constants.ENTRY_NODE.increaseThreadNum();
			Constants.ENTRY_NODE.addPassRequest(count);
		}

		// Handle pass event with registered entry callback handlers.
		//请求通过后回调
		for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
			handler.onPass(context, resourceWrapper, node, count, args);
		}
	} catch (PriorityWaitException ex) {
		//优先级等待异常这里没有增加请求失败的数量
		node.increaseThreadNum();
		//请求来源节点判断
		if (context.getCurEntry().getOriginNode() != null) {
			// Add count for origin node.
			//来源节点不为空 来源节点的 线程数 +1
			context.getCurEntry().getOriginNode().increaseThreadNum();
		}
		//是否是入口资源类型
		if (resourceWrapper.getEntryType() == EntryType.IN) {
			// Add count for global inbound entry node for global statistics.
			//如果是入口资源类型 全局线程数 +1
			Constants.ENTRY_NODE.increaseThreadNum();
		}
		// Handle pass event with registered entry callback handlers.
		//请求通过后回调
		for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
			handler.onPass(context, resourceWrapper, node, count, args);
		}
	} catch (BlockException e) {
		// Blocked, set block exception to current entry.
		//阻塞 没有通过异常  将异常信息保存在 当前的 entry 中
		context.getCurEntry().setBlockError(e);

		// Add block count.
		//增加阻塞数量
		node.increaseBlockQps(count);
		//请求来源节点判断
		if (context.getCurEntry().getOriginNode() != null) {
			//请求来源节点 阻塞数量 +1
			context.getCurEntry().getOriginNode().increaseBlockQps(count);
		}
		//是否是入口资源类型
		if (resourceWrapper.getEntryType() == EntryType.IN) {
			// Add count for global inbound entry node for global statistics.
			//如果是入口资源类型 全局阻塞数 +1
			Constants.ENTRY_NODE.increaseBlockQps(count);
		}

		// Handle block event with registered entry callback handlers.
		//回调
		for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
			handler.onBlocked(e, context, resourceWrapper, node, count, args);
		}

		throw e;
	} catch (Throwable e) {
		// Unexpected internal error, set error to current entry.
		//错误设置到 当前 Entry
		context.getCurEntry().setError(e);

		throw e;
	}
}

StatisticSlot#entry 方法源码解析

StatisticNode#addPassRequest 方法中,分别对当前链路和当前资源的计数器都进行了加 1 操作。

//com.alibaba.csp.sentinel.node.StatisticNode#addPassRequest
@Override
public void addPassRequest(int count) {
    // DefaultNode的计数器,代表当前链路的 计数器
    super.addPassRequest(count);
    // ClusterNode计数器,代表当前资源的 总计数器
    this.clusterNode.addPassRequest(count);
}

至此,统计数据部分的 Slot 已经分析完毕了,下一篇我们将进入规则校验的相关 Slot 了,根据 Sentinel 官方提提供的 wiki 文档,规则校验部分的 Slot 执行顺序依次是:

  • ParamFlowSlot:负责热点参数的限流规则。
  • SystemSlot:负责系统保护规则。
  • AuthoritySlot:负责授权(来源控制)的规则。
  • FlowSlot:负责限流的规则。
  • DegradeSlot:负责熔断降级的规则。

欢迎提出建议及对错误的地方指出纠正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值