2.2.2 Node
Node是一个接口,用于定义统计资源实时指标数据的方法,可以对外部屏蔽滑动窗口的存在。Node接口的不同实现类被用在不同维度为资源统计实时指标数据。
Node接口源码如下。
• totalRequest:获取请求总数。
• totalPass:获取被放行的请求总数。
• totalSuccess:获取响应成功的请求总数,即被放行且未出现异常的请求总数。
• blockRequest:获取被拒绝的请求总数。
• totalException:获取发生异常的请求总数。
• passQps:获取当前时间窗口被放行的请求总数。
• blockQps:获取当前时间窗口被拒绝的请求总数。
• totalQps:获取当前时间窗口的请求总数。
• successQps:获取当前时间窗口响应成功的请求总数。
• maxSuccessQps:获取一段时间内最大的successQps,例如,若秒级滑动窗口的数组大小的默认配置为2,则获取数组中successQps值最大的一个。
• exceptionQps:获取当前时间窗口发生异常的请求总数。
• avgRt:获取平均耗时。
• minRt:获取最小耗时。
• curThreadNum:获取当前并行占用的线程数。
• previousBlockQps:获取前一个时间窗口的blockQps。
• previousPassQps:获取前一个时间窗口的passQps。
• addPassRequest:当前时间窗口被放行的请求总数+count。
• addRtAndSuccess:当前时间窗口响应成功的请求总数+success及总耗时+rt。
• increaseBlockQps:当前时间窗口被拒绝的请求总数+1。
• increaseExceptionQps:当前时间窗口发生异常的请求总数+1。
• increaseThreadNum:并行占用线程数+1。
• decreaseThreadNum:并行占用线程数-1。
Node接口的几个实现类为StatisticNode、DefaultNode、ClusterNode、EntranceNode,它们的关系如图2.5所示。
图2.5 Node接口及其实现类
➢ StatisticNode
Statistic是统计的意思,StatisticNode类是Node接口的实现类,封装实现实时指标数据统计,其源码如下。
• rollingCounterInSecond:秒级滑动窗口。
• rollingCounterInMinute:分钟级滑动窗口。
• curThreadNum:并行占用线程计数器。
一个StatisticNode实例包含一个秒级滑动窗口、一个分钟级滑动窗口及一个并行占用线程计数器。秒级滑动窗口用于统计实时的指标数据,分钟级滑动窗口用于保存最近一分钟内的历史指标数据,并行占用线程计数器用于统计实时占用的线程数。
提示:第4章会详细介绍滑动窗口及Sentinel如何实现资源指标数据统计。
需要注意的是,“分钟级滑动窗口用于保存最近一分钟内的历史指标数据”这句话并不完全正确,因为它统计的指标数据并不是从秒级滑动窗口得来的。
以StatisticNode类实现Node接口的addRtAndSuccess方法为例,addRtAndSuccess方法中分别调用了两个滑动窗口的addSuccess方法和addRT方法,代码如下。
StatisticNode实例的分钟级滑动窗口和秒级滑动窗口统计的指标数据分别有不同的用途,如下所示。
(1)当需要获取前一秒被拒绝的请求总数时,需要从分钟级滑动窗口中获取,代码如下。
(2)当需要获取当前一秒内已经被拒绝的请求总数时,需要从秒级滑动窗口中获取,代码如下。
(3)当需要获取当前一秒内的最小耗时时,需要从秒级滑动窗口中获取,代码如下。
从StatisticNode类定义的curThreadNum变量可以看出,StatisticNode实例还负责统计资源并行占用的线程数,这个功能可用于实现信号量隔离,按资源所能并发占用的最大线程数实现限流。
除响应式编程外,每处理一个请求需要占用一个线程,那么可以在处理请求之前将curThreadNum自增1,在处理完请求后将curThreadNum自减1。当同时处理N个请求时,curThreadNum的值就为N。
如果配置Tomcat处理请求的线程池大小为N,则可以设置某接口允许并行占用的线程数小于N,使该接口不能同时使用这N个线程,避免因为该接口响应慢将N个线程都阻塞而导致的应用无法处理其他请求的情况发生。
➢ DefaultNode
从调用树的根节点出发,到每个叶子节点的路径都是一条调用链,而每条调用链中除根节点外,都可能有相同的入口节点。DefaultNode实例用于统计同一资源、不同调用链入口的实时指标数据。
DefaultNode类是StatisticNode类的子类,其构造方法要求传入资源ID,表示该DefaultNode实例用于统计该资源的实时指标数据。DefaultNode类的源码如下。
• id:资源ID,ResourceWrapper实例。
• childList:childList是一个Node集合,用于存放子节点和构造调用树。
• clusterNode:类型为ClusterNode,引用当前资源全局唯一的ClusterNode实例。
一个资源可能有多个DefaultNode实例,而是否有多个DefaultNode实例取决于该资源是否被多个不同入口节点的调用链包含。Sentinel这样做的目的是,实现可按不同调用链入口对资源采取不同的流量控制策略。
我们回顾一下Sentinel的基本使用方法,代码如下。
在本例中,doBusiness方法就是被Sentinel保护的资源,资源名称为demo,调用链入口名称为myContextt。如果存在另一个入口名称为myContextt2的调用链也使用资源demo,那么Sentinel就会对同一个资源demo创建两个DefaultNode实例。
➢ ClusterNode
Sentinel使用ClusterNode统计每个资源的全局指标数据,以及针对不同调用来源,分别统计资源的指标数据。
资源的全局指标数据统计不区分调用链入口,也不区分调用来源,一个资源有且仅有一个ClusterNode实例。
ClusterNode类的源码如下。
• name:资源名称。
• resourceType:资源类型。
• originCountMap:key为调用来源,value为StatisticNode实例。
ClusterNode类没有使用ResourceWrapper实例表示资源,而是直接使用了资源名称,这可能是历史版本的原因,虽然DefaultNode类使用ResourceWrapper实例表示资源,但是在统计资源的实时指标数据时并不区分流量类型(IN/OUT)。
➢ EntranceNode
EntranceNode类是DefaultNode类的子类,调用链的入口节点及调用树的根节点的类型都是EntranceNode。EntranceNode类的源码如下。
在一个Web应用中,一个接口就是一个资源。Sentinel通过WebMvc拦截器拦截每个接口的请求,为接口披上“保护伞”,并给接口设置相同的入口名称,即sentinel_spring_ web_context,代码如下。
Sentinel会创建一个名称为sentinel_spring_web_context的入口节点,类型为EntranceNode。一个Web应用可能有多个接口,但它们的入口节点都是sentinel_spring_web_context,而入口节点的childList就用于存储每个接口的DefaultNode实例。
如果想统计一个Web应用的所有接口(不一定是所有接口,没有被调用过的接口不会创建对应的DefaultNode实例)的总QPS,那么只需要调用入口节点(EntranceNode实例)的totalQps方法就能获取。
EntranceNode类的totalQps方法源码如下。
调用链入口节点、资源的DefaultNode节点、资源的ClusterNode节点与滑动窗口的关系如图2.6所示。
图2.6 各类型节点与滑动窗口的关系