安庆大理运城常德铜陵江西
投稿投诉
江西南阳
嘉兴昆明
铜陵滨州
广东西昌
常德梅州
兰州阳江
运城金华
广西萍乡
大理重庆
诸暨泉州
安庆南充
武汉辽宁

全网最全Sentinel资源指标数据统计实现源码解析

12月4日 不星湖投稿
  节点选择器:NodeSelectorSlot
  NodeSelectorSlot负责为资源的首次访问创建DefaultNode,以及维护Context。curNode和调用树。NodeSelectorSlot被放在ProcessorSlotChain链表的第一个位置,这是因为后续的ProcessorSlot都需要依赖这个ProcessorSlot。NodeSelectorSlot源码如下:publicclassNodeSelectorSlotextendsAbstractLinkedProcessorSlotObject{Context的name资源的DefaultNodeprivatevolatileMapString,DefaultNodemapnewHashMap(10);入口方法Overridepublicvoidentry(Contextcontext,ResourceWrapperresourceWrapper,Objectobj,intcount,booleanprioritized,Object。。。args)throwsThrowable{使用Context的名称作为key缓存资源的DefaultNodeDefaultNodenodemap。get(context。getName());if(nodenull){synchronized(this){nodemap。get(context。getName());if(nodenull){为资源创建DefaultNodenodenewDefaultNode(resourceWrapper,null);替换mapHashMapString,DefaultNodecacheMapnewHashMap(map。size());cacheMap。putAll(map);cacheMap。put(context。getName(),node);mapcacheM绑定调用树((DefaultNode)context。getLastNode())。addChild(node);}}}替换Context的curNode为当前DefaultNodecontext。setCurNode(node);fireEntry(context,resourceWrapper,node,count,prioritized,args);}出口方法什么也不做Overridepublicvoidexit(Contextcontext,ResourceWrapperresourceWrapper,intcount,Object。。。args){fireExit(context,resourceWrapper,count,args);}}
  如源码所示,map字段是一个非静态字段,意味着每个NodeSelectorSlot都有一个map。由于一个资源对应一个ProcessorSlotChain,而一个ProcessorSlotChain只创建一个NodeSelectorSlot,并且map缓存DefaultNode使用的key并非资源ID,而是Context。name,所以map的作用是缓存针对同一资源为不同调用链路入口创建的DefaultNode。
  在entry方法中,首先根据Context。name从map获取当前调用链路入口的资源DefaultNode,如果资源第一次被访问,也就是资源的ProcessorSlotChain第一次被创建,那么这个map是空的,就会加锁为资源创建DefaultNode,如果资源不是首次被访问,但却首次作为当前调用链路(Context)的入口资源,也需要加锁为资源创建一个DefaultNode。可见,Sentinel会为同一资源ID创建多少个DefaultNode取决于有多少个调用链使用其作为入口资源,直白点就是同一资源存在多少个DefaultNode取决于Context。name有多少种不同取值,这就是为什么说一个资源可能有多个DefaultNode的原因。
  为什么这么设计呢?
  举个例子,对同一支付接口,我们需要使用SpringMVC暴露给前端访问,同时也可能会使用Dubbo暴露给其它内部服务调用。Sentinel的WebMVC适配器在调用链路入口创建名为sentinelspringwebcontext的Context,与Sentinel的Dubbo适配器调用ContextUtilenter方法创建的Context名称不同。针对这种情况,我们可以实现只限制SpringMVC进来的流量,也就是限制前端发起接口调用的QPS、并行占用的线程数等。
  NodeSelectorSlotentry方法最难以理解的就是实现绑定调用树这行代码:((DefaultNode)context。getLastNode())。addChild(node);
  这行代码分两种情况分析更容易理解,我们就以Sentinel提供的Demo为例进行分析。一般情况
  Sentinel的sentineldemo模块下提供了多种使用场景的Demo,我们选择sentineldemospringwebmvc这个Demo为例,该Demo下有一个hello接口,其代码如下。RestControllerpublicclassWebMvcTestController{GetMapping(hello)publicStringapiHello()throwsBlockException{doBusiness();returnHello!;}}
  我们不需要添加任何规则,只是为了调试Sentinel的源码。将demo启动起来后,在浏览器访问hello接口,在NodeSelectorSlotentry方法的绑定调用树这一行代码下断点,观察此时Context的字段信息。正常情况下我们可以看到如下图所示的结果。
  从上图中可以看出,此时的Context。entranceNode的子节点为空(childList的大小为0),并且当前CtEntry父、子节点都是Null(curEntry字段)。当绑定调用树这一行代码执行完成后,Context的字段信息如下图所示:
  从上图可以看出,NodeSelectorSlot为当前资源创建的DefaultNode被添加到了Context。entranceNode的子节点。entranceNode类型为EntranceNode,在调用ContextUtilenter方法时创建,在第一次创建名为sentinelspringwebcontext的Context时创建,相同名称的Context都使用同一个EntranceNode。并且该EntranceNode在创建时会被添加到Constant。ROOT。
  此时,Constant。ROOT、Context。entranceNode、当前访问资源的DefaultNode构造成的调用树如下:ROOT(machineroot)EntranceNode(contextname:sentinelspringwebcontext)DefaultNode(resourcename:GET:hello)
  如果我们现在再访问Demo的其他接口,例如访问err接口,那么生成的调用树就会变成如下:ROOT(machineroot)EntranceNode(contextname:sentinelspringwebcontext)DefaultNode(resourcename:GET:hello)DefaultNode(resourcename:GET:err)
  Context。entranceNode将会存储Web项目的所有资源(接口)的DefaultNode。存在多次SphUentry的情况
  比如我们在一个服务中添加了Sentinel的WebMVC适配模块的依赖,也添加了Sentinel的OpenFeign适配模块的依赖,并且我们使用OpenFeign调用内部其他服务的接口,那么就会存在一次调用链路上出现多次调用SphUentry方法的情况。
  首先webmvc适配器在接收客户端请求时会调用一次SphUentry,在处理客户端请求时可能需要使用OpenFeign调用其它服务的接口,那么在发起接口调用时,Sentinel的OpenFeign适配器也会调用一次SphUentry。
  现在我们将Demo的hello接口修改一下,将hello接口调用的doBusiness方法也作为资源使用Sentinel保护起来,改造后的hello接口代码如下:RestControllerpublicclassWebMvcTestController{GetMapping(hello)publicStringapiHello()throwsBlockException{ContextUtil。enter(mycontext);Etry{entrySphU。entry(POST:http:wujiuye。comhello2,EntryType。OUT);这里是被包装的代码doBusiness();returnHello!;end}catch(Exceptione){if(!(einstanceofBlockException)){Tracer。trace(e);}}finally{if(entry!null){entry。exit(1);}ContextUtil。exit();}}}
  我们可将doBusiness方法看成是远程调用,例如调用第三方的接口,接口名称为http:wujiuye。comhello2,使用POST方式调用,那么我们可以使用POST:http:wujiuye。comhello2作为资源名称,并将流量类型设置为OUT类型。上下文名称取名为mycontext。
  现在启动demo,使用浏览器访问hello接口。当代码执行到apiHello方法时,在NodeSelectorSlotentry方法的绑定调用树这一行代码下断点。当绑定调用树这行代码执行完成后,Context的字段信息如下图所示。
  如图所示,Sentinel并没有创建名称为mycontext的Context,还是使用应用接收到请求时创建名为sentinelspringwebcontext的Context,所以处理浏览器发送过来的请求的GET:hello资源是本次调用链路的入口资源,Sentinel在调用链路入口处创建Context之后不再创建新的Context。
  由于之前并没有为名称为POST:http:wujiuye。comhello2的资源创建ProcessorSlotChain,所以SphUentry会为该资源创建一个ProcessorSlotChain,也就会为该ProcessorSlotChain创建一个NodeSelectorSlot。在执行到NodeSelectorSlotentry方法时,就会为该资源创建一个DefaultNode,而将该资源的DefaultNode绑定到节点树后,该资源的DefaultNode就会成为GET:hello资源的DefaultNode的子节点,调用树如下。ROOT(machineroot)EntranceNode(name:sentinelspringwebcontext)DefaultNode(GET:hello)。。。。。。。。。DefaultNode(POST:hello2)
  此时,当前调用链路上也已经存在两个CtEntry,这两个CtEntry构造一个双向链表,如下图所示。
  虽然存在两个CtEntry,但此时Context。curEntry指向第二个CtEntry,第二个CtEntry在apiHello方法中调用SphUentry方法时创建,当执行完doBusiness方法后,调用当前CtEntryexit方法,由该CtEntry将Context。curEntry还原为该CtEntry的父CtEntry。这有点像入栈和出栈操作,例如栈帧在Java虚拟机栈的入栈和出栈,调用方法时方法的栈帧入栈,方法执行完成栈帧出栈。
  NodeSelectorSlotentry方法我们还有一行代码没有分析,就是将当前创建的DefaultNode设置为Context的当前节点,代码如下:替换Context。curNode为当前DefaultNodecontext。setCurNode(node);
  替换Context。curNode为当前资源DefaultNode这行代码就是将当前创建的DefaultNode赋值给当前CtEntry。curNode。对着上图理解就是,将资源GET:hello的DefaultNode赋值给第一个CtEntry。curNode,将资源POST:http:wujiuye。comhello2的DefaultNode赋值给第二个CtEntry。curNode。
  要理解Sentinel构造CtEntry双向链表的目的,首先我们需要了解调用ContextgetCurNode方法获取当前资源的DefaultNode可以做什么。
  Tracertracer方法用于记录异常。以异常指标数据统计为例,在发生非Block异常时,Tracertracer需要从Context获取当前资源的DefaultNode,通知DefaultNode记录异常,同时DefaultNode也会通知ClusterNode记录记录,如下代码所示。publicclassDefaultNodeextendsStatisticNode{。。。。。。OverridepublicvoidincreaseExceptionQps(intcount){super。increaseExceptionQps(count);this。clusterNode。increaseExceptionQps(count);}}
  这个例子虽然简单,但也足以说明Sentinel构造CtEntry双向链表的目的。ClusterNode构造器:ClusterBuilderSlotClusterNode出现的背景
  在一个资源的ProcessorSlotChain中,NodeSelectorSlot负责为资源创建DefaultNode,这个DefaultNode仅限同名的Context使用。所以一个资源可能会存在多个DefaultNode,那么想要获取一个资源的总的QPS就必须要遍历这些DefaultNode。为了性能考虑,Sentinel会为每个资源创建一个全局唯一的ClusterNode,用于统计资源的全局并行占用线程数、QPS、异常总数等指标数据。ClusterBuilderSlot
  与NodeSelectorSlot的职责相似,ClusterBuilderSlot的职责是为资源创建全局唯一的ClusterNode,仅在资源第一次被访问时创建。ClusterBuilderSlot还会将ClusterNode赋值给DefaultNode。clusterNode,由DefaultNode持有ClusterNode,负责管理ClusterNode的指标数据统计。这点也是ClusterBuilderSlot在ProcessorSlotChain链表中必须排在NodeSelectorSlot之后的原因,即必须先有DefaultNode,才能将ClusterNode交给DefaultNode管理。
  ClusterBuilderSlot的源码比较多,本篇只分析其实现ProcessorSlot接口的entry和exit方法。ClusterBuilderSlot删减后的源码如下。publicclassClusterBuilderSlotextendsAbstractLinkedProcessorSlotDefaultNode{资源ClusterNodeprivatestaticvolatileMapResourceWrapper,ClusterNodeclusterNodeMapnewHashMap();privatestaticfinalObjectlocknewObject();非静态,一个资源对应一个ProcessorSlotChain,所以一个资源共用一个ClusterNodeprivatevolatileClusterNodeclusterNOverridepublicvoidentry(Contextcontext,ResourceWrapperresourceWrapper,DefaultNodenode,intcount,booleanprioritized,Object。。。args)throwsThrowable{if(clusterNodenull){synchronized(lock){if(clusterNodenull){创建ClusterNodeclusterNodenewClusterNode(resourceWrapper。getName(),resourceWrapper。getResourceType());添加到缓存HashMapResourceWrapper,ClusterNodenewMapnewHashMap(Math。max(clusterNodeMap。size(),16));newMap。putAll(clusterNodeMap);newMap。put(node。getId(),clusterNode);clusterNodeMapnewM}}}node为NodeSelectorSlot传递过来的DefaultNodenode。setClusterNode(clusterNode);如果origin不为空,则为远程创建一个StatisticNodeif(!。equals(context。getOrigin())){NodeoriginNodenode。getClusterNode()。getOrCreateOriginNode(context。getOrigin());context。getCurEntry()。setOriginNode(originNode);}fireEntry(context,resourceWrapper,node,count,prioritized,args);}Overridepublicvoidexit(Contextcontext,ResourceWrapperresourceWrapper,intcount,Object。。。args){fireExit(context,resourceWrapper,count,args);}}
  ClusterBuilderSlot使用一个Map缓存资源的ClusterNode,并且用一个非静态的字段维护当前资源的ClusterNode。因为一个资源只会创建一个ProcessorSlotChain,意味着ClusterBuilderSlot也只会创建一个,那么让ClusterBuilderSlot持有该资源的ClusterNode就可以省去每次都从Map中获取的步骤,这当然也是Sentinel为性能做出的努力。
  ClusterBuilderSlotentry方法的node参数由前一个ProcessorSlot传递过来,也就是NodeSelectorSlot传递过来的DefaultNode。ClusterBuilderSlot将ClusterNode赋值给DefaultNode。clusterNode,那么后续的ProcessorSlot就能从node参数中取得ClusterNode。DefaultNode与ClusterNode的关系如下图所示。
  ClusterNode有一个Map类型的字段用来缓存origin与StatisticNode的映射,代码如下:publicclassClusterNodeextendsStatisticNode{privatefinalSprivatefinalintresourceTprivateMapString,StatisticNodeoriginCountMapnewHashMap();}
  如果上游服务在调用当前服务的接口传递origin字段过来,例如可在http请求头添加Suser参数,或者Dubborpc调用在请求参数列表加上application参数,那么ClusterBuilderSlot就会为ClusterNode创建一个StatisticNode,用来统计当前资源被远程服务调用的指标数据。
  例如,当origin表示来源应用的名称时,对应的StatisticNode统计的就是针对该调用来源的指标数据,可用来查看哪个服务访问这个接口最频繁,由此可实现按调用来源限流。
  ClusterNodegetOrCreateOriginNode方法源码如下:publicNodegetOrCreateOriginNode(Stringorigin){StatisticNodestatisticNodeoriginCountMap。get(origin);if(statisticNodenull){try{lock。lock();statisticNodeoriginCountMap。get(origin);if(statisticNodenull){statisticNodenewStatisticNode();这几行代码在Sentinel中随处可见HashMapString,StatisticNodenewMapnewHashMap(originCountMap。size()1);newMap。putAll(originCountMap);newMap。put(origin,statisticNode);originCountMapnewM}}finally{lock。unlock();}}returnstatisticN}
  为了便于使用,ClusterBuilderSlot会将调用来源(origin)的StatisticNode赋值给Context。curEntry。originNode,后续的ProcessorSlot可调用ContextgetCurEntrygetOriginNode方法获取该StatisticNode。这里我们可以得出一个结论,如果我们自定义的ProcessorSlot需要用到调用来源的StatisticNode,那么在构建ProcessorSlotChain时,我们必须要将这个自定义ProcessorSlot放在ClusterBuilderSlot之后。资源指标数据统计:StatisticSlot
  StatisticSlot才是实现资源各项指标数据统计的ProcessorSlot,它与NodeSelectorSlot、ClusterBuilderSlot组成了资源指标数据统计流水线,分工明确。
  首先NodeSelectorSlot为资源创建DefaultNode,将DefaultNode向下传递,ClusterBuilderSlot负责给资源的DefaultNode加工,添加ClusterNode这个零部件,再将DefaultNode向下传递给StatisticSlot,如下图所示:
  StatisticSlot在统计指标数据之前会先调用后续的ProcessorSlot,根据后续ProcessorSlot判断是否需要拒绝该请求的结果决定记录哪些指标数据,这也是为什么Sentinel设计的责任链需要由前一个ProcessorSlot在entry或者exit方法中调用fireEntry或者fireExit完成调用下一个ProcessorSlot的entry或exit方法,而不是使用for循环遍历调用ProcessorSlot的原因。每个ProcessorSlot都有权决定是先等后续的ProcessorSlot执行完成再做自己的事情,还是先完成自己的事情再让后续ProcessorSlot执行,与流水线有所区别。
  StatisticSlot源码框架如下:publicclassStatisticSlotextendsAbstractLinkedProcessorSlotDefaultNode{Overridepublicvoidentry(Contextcontext,ResourceWrapperresourceWrapper,DefaultNodenode,intcount,booleanprioritized,Object。。。args)throwsThrowable{try{Dosomechecking。fireEntry(context,resourceWrapper,node,count,prioritized,args);。。。。。}catch(PriorityWaitExceptionex){。。。。。}catch(BlockExceptione){。。。。}catch(Throwablee){。。。。。}}Overridepublicvoidexit(Contextcontext,ResourceWrapperresourceWrapper,intcount,Object。。。args){DefaultNodenode(DefaultNode)context。getCurNode();。。。。fireExit(context,resourceWrapper,count);}}entry:先调用fireEntry方法完成调用后续的ProcessorSlotentry方法,根据后续的ProcessorSlot是否抛出BlockException决定记录哪些指标数据,并将资源并行占用的线程数加1。exit:若无任何异常,则记录响应成功、请求执行耗时,将资源并行占用的线程数减1。entry方法
  第一种情况:当后续的ProcessorSlot未抛出任何异常时,表示不需要拒绝该请求,放行当前请求。
  当请求可正常通过时,需要将当前资源并行占用的线程数增加1、当前时间窗口被放行的请求总数加1,代码如下:Requestpassed,addthreadcountandpasscount。node。increaseThreadNum();node。addPassRequest(count);
  如果调用来源不为空,也将调用来源的StatisticNode的当前并行占用线程数加1、当前时间窗口被放行的请求数加1,代码如下:if(context。getCurEntry()。getOriginNode()!null){Addcountfororiginnode。context。getCurEntry()。getOriginNode()。increaseThreadNum();context。getCurEntry()。getOriginNode()。addPassRequest(count);}
  如果流量类型为IN,则将资源全局唯一的ClusterNode的并行占用线程数、当前时间窗口被放行的请求数都增加1,代码如下:if(resourceWrapper。getEntryType()EntryType。IN){Addcountforglobalinboundentrynodeforglobalstatistics。Constants。ENTRYNODE。increaseThreadNum();Constants。ENTRYNODE。addPassRequest(count);}
  回调所有ProcessorSlotEntryCallbackonPass方法,代码如下:Handlepasseventwithregisteredentrycallbackhandlers。for(ProcessorSlotEntryCallbackDefaultNodehandler:StatisticSlotCallbackRegistry。getEntryCallbacks()){handler。onPass(context,resourceWrapper,node,count,args);}
  可调用StatisticSlotCallbackRegistryaddEntryCallback静态方法注册ProcessorSlotEntryCallback,ProcessorSlotEntryCallback接口的定义如下:publicinterfaceProcessorSlotEntryCallbackT{voidonPass(Contextcontext,ResourceWrapperresourceWrapper,Tparam,intcount,Object。。。args)throwsEvoidonBlocked(BlockExceptionex,Contextcontext,ResourceWrapperresourceWrapper,Tparam,intcount,Object。。。args);}onPass:该方法在请求被放行时被回调执行。onBlocked:该方法在请求被拒绝时被回调执行。
  第二种情况:捕获到类型为PriorityWaitException的异常。
  这是特殊情况,在需要对请求限流时,只有使用默认流量效果控制器才可能会抛出PriorityWaitException异常,这部分内容将在分析FlowSlot的实现源码时再作分析。
  当捕获到PriorityWaitException异常时,说明当前请求已经被休眠了一会了,但请求还是允许通过的,只是不需要为DefaultNode记录这个请求的指标数据了,只自增当前资源并行占用的线程数,同时,DefaultNode也会为ClusterNode自增并行占用的线程数。最后也会回调所有ProcessorSlotEntryCallbackonPass方法。这部分源码如下。node。increaseThreadNum();if(context。getCurEntry()。getOriginNode()!null){Addcountfororiginnode。context。getCurEntry()。getOriginNode()。increaseThreadNum();}if(resourceWrapper。getEntryType()EntryType。IN){Addcountforglobalinboundentrynodeforglobalstatistics。Constants。ENTRYNODE。increaseThreadNum();}Handlepasseventwithregisteredentrycallbackhandlers。for(ProcessorSlotEntryCallbackDefaultNodehandler:StatisticSlotCallbackRegistry。getEntryCallbacks()){handler。onPass(context,resourceWrapper,node,count,args);}
  第三种情况:捕获到BlockException异常,BlockException异常只在需要拒绝请求时抛出。
  当捕获到BlockException异常时,将异常记录到调用链路上下文的当前Entry(StatisticSlot的exit方法会用到),然后调用DefaultNodeincreaseBlockQps方法记录当前请求被拒绝,将当前时间窗口的blockqps这项指标数据的值加1。如果调用来源不为空,让调用来源的StatisticsNode也记录当前请求被拒绝;如果流量类型为IN,则让用于统计所有资源指标数据的ClusterNode也记录当前请求被拒绝。这部分的源码如下:Blocked,setblockexceptiontocurrententry。context。getCurEntry()。setError(e);Addblockcount。node。increaseBlockQps(count);if(context。getCurEntry()。getOriginNode()!null){context。getCurEntry()。getOriginNode()。increaseBlockQps(count);}if(resourceWrapper。getEntryType()EntryType。IN){Addcountforglobalinboundentrynodeforglobalstatistics。Constants。ENTRYNODE。increaseBlockQps(count);}Handleblockeventwithregisteredentrycallbackhandlers。for(ProcessorSlotEntryCallbackDefaultNodehandler:StatisticSlotCallbackRegistry。getEntryCallbacks()){handler。onBlocked(e,context,resourceWrapper,node,count,args);}
  StatisticSlot捕获BlockException异常只是为了收集被拒绝的请求,BlockException异常还是会往上抛出。抛出异常的目的是为了拦住请求,让入口处能够执行到catch代码块完成请求被拒绝后的服务降级处理。
  第四种情况:捕获到其它异常。
  其它异常并非指业务异常,因为此时业务代码还未执行,而业务代码抛出的异常是通过调用Tracertrace方法记录的。
  当捕获到非BlockException异常时,除PriorityWaitException异常外,其它类型的异常都同样处理。让DefaultNode记录当前请求异常,将当前时间窗口的exceptionqps这项指标数据的值加1。调用来源的StatisticsNode、用于统计所有资源指标数据的ClusterNode也记录下这个异常。这部分源码如下:Unexpectederror,seterrortocurrententry。context。getCurEntry()。setError(e);Thisshouldnothappen。node。increaseExceptionQps(count);if(context。getCurEntry()。getOriginNode()!null){context。getCurEntry()。getOriginNode()。increaseExceptionQps(count);}if(resourceWrapper。getEntryType()EntryType。IN){Constants。ENTRYNODE。increaseExceptionQps(count);}exit方法
  exit方法被调用时,要么请求被拒绝,要么请求被放行并且已经执行完成,所以exit方法需要知道当前请求是否正常执行完成,这正是StatisticSlot在捕获异常时将异常记录到当前Entry的原因,exit方法中通过Context可获取到当前CtEntry,从当前CtEntry可获取entry方法中写入的异常。
  exit方法源码如下(有删减):Overridepublicvoidexit(Contextcontext,ResourceWrapperresourceWrapper,intcount,Object。。。args){DefaultNodenode(DefaultNode)context。getCurNode();if(context。getCurEntry()。getError()null){计算耗时longrtTimeUtil。currentTimeMillis()context。getCurEntry()。getCreateTime();记录执行耗时与成功总数node。addRtAndSuccess(rt,count);if(context。getCurEntry()。getOriginNode()!null){context。getCurEntry()。getOriginNode()。addRtAndSuccess(rt,count);}自减当前资源占用的线程数node。decreaseThreadNum();origin不为空if(context。getCurEntry()。getOriginNode()!null){context。getCurEntry()。getOriginNode()。decreaseThreadNum();}流量类型为in时if(resourceWrapper。getEntryType()EntryType。IN){Constants。ENTRYNODE。addRtAndSuccess(rt,count);Constants。ENTRYNODE。decreaseThreadNum();}}Handleexiteventwithregisteredexitcallbackhandlers。CollectionProcessorSlotExitCallbackexitCallbacksStatisticSlotCallbackRegistry。getExitCallbacks();for(ProcessorSlotExitCallbackhandler:exitCallbacks){handler。onExit(context,resourceWrapper,count,args);}fireExit(context,resourceWrapper,count);}
  exit方法中通过Context可获取当前资源的DefaultNode,如果entry方法中未出现异常,那么说明请求是正常完成的,在请求正常完成情况下需要记录请求的执行耗时以及响应是否成功,可将当前时间减去调用链路上当前Entry的创建时间作为请求的执行耗时。资源指标数据的记录过程
  ClusterNode才是一个资源全局的指标数据统计节点,但我们并未在StatisticSlotentry方法与exit方法中看到其被使用。因为ClusterNode被ClusterBuilderSlot交给了DefaultNode掌管,在DefaultNode的相关指标数据收集方法被调用时,ClusterNode的对应方法也会被调用,如下代码所示:publicclassDefaultNodeextendsStatisticNode{。。。。。。privateClusterNodeclusterNOverridepublicvoidaddPassRequest(intcount){super。addPassRequest(count);this。clusterNode。addPassRequest(count);}}
  记录某项指标数据指的是:针对当前请求,记录当前请求的某项指标数据,例如请求被放行、请求被拒绝、请求的执行耗时等。
  假设当前请求被成功处理,StatisticSlot会调用DefaultNodeaddRtAndSuccess方法记录请求处理成功、并且记录处理请求的耗时,DefaultNode先调用父类的addRtAndSuccess方法,然后DefaultNode会调用ClusterNodeaddRtAndSuccess方法。ClusterNode与DefaultNode都是StatisticNode的子类,StatisticNodeaddRtAndSuccess方法源码如下:OverridepublicvoidaddRtAndSuccess(longrt,intsuccessCount){秒级滑动窗口rollingCounterInSecond。addSuccess(successCount);rollingCounterInSecond。addRT(rt);分钟级的滑动窗口rollingCounterInMinute。addSuccess(successCount);rollingCounterInMinute。addRT(rt);}
  rollingCounterInSecond是一个秒级的滑动窗口,rollingCounterInMinute是一个分钟级的滑动窗口,类型为ArrayMetric。分钟级的滑动窗口一共有60个MetricBucket,每个MetricBucket都被WindowWrap包装,每个MetricBucket统计一秒钟内的各项指标数据,如下图所示:
  当调用rollingCounterInMinuteaddSuccess方法时,由ArrayMetric根据当前时间戳获取当前时间窗口的MetricBucket,再调用MetricBucketaddSuccess方法将success这项指标的值加上方法参数传递进来的值(一般是1)。MetricBucket使用LongAdder记录各项指标数据的值。
  Sentinel在MetricEvent枚举类中定义了Sentinel会收集哪些指标数据,MetricEvent枚举类的源码如下:publicenumMetricEvent{PASS,BLOCK,EXCEPTION,SUCCESS,RT,OCCUPIEDPASS}pass指标:请求被放行的总数block:请求被拒绝的总数exception:请求处理异常的总数success:请求被处理成功的总数rt:被处理成功的请求的总耗时occupiedpass:预通过总数(前一个时间窗口使用了当前时间窗口的passQps)
  其它的指标数据都可通过以上这些指标数据计算得出,例如,平均耗时可根据总耗时除以成功总数计算得出。资源指标数据统计总结一个调用链路上只会创建一个Context,在调用链路的入口创建(一个调用链路上第一个被Sentinel保护的资源)。一个Context名称只创建一个EntranceNode,也是在调用链路的入口创建,调用Contextenter方法时创建。与方法调用的入栈出栈一样,一个线程上调用多少次SphUentry方法就会创建多少个CtEntry,前一个CtEntry作为当前CtEntry的父节点,当前CtEntry作为前一个CtEntry的子节点,构成一个双向链表。Context。curEntry保存的是当前的CtEntry,在调用当前的CtEntryexit方法时,由当前CtEntry将Context。curEntry还原为当前CtEntry的父节点CtEntry。一个调用链路上,如果多次调用SphUentry方法传入的资源名称都相同,那么只会创建一个DefaultNode,如果资源名称不同,会为每个资源名称创建一个DefaultNode,当前DefaultNode会作为调用链路上的前一个DefaultNode的子节点。一个资源有且只有一个ProcessorSlotChain,一个资源有且只有一个ClusterNode。一个ClusterNode负责统计一个资源的全局指标数据。StatisticSlot负责记录请求是否被放行、请求是否被拒绝、请求是否处理异常、处理请求的耗时等指标数据,在StatisticSlot调用DefaultNode用于记录某项指标数据的方法时,DefaultNode也会调用ClusterNode的相对应方法,完成两份指标数据的收集。DefaultNode统计当前资源的各项指标数据的维度是同一个Context(名称相同),而ClusterNode统计当前资源各项指标数据的维度是全局。
投诉 评论 转载

李隆基杨贵妃曾在此欢歌燕舞,如今是城市公园,市民文化浓郁来西安,人们一定会去打卡钟鼓楼、大小雁塔、大唐不夜城,来领略盛唐的风采。但除此之外,在西安城墙以东、西安交大对面,还有一个地方不仅能了解盛唐的知识,还能感受西安市民文化,那就是……狂轰4613,辽宁双子星大爆发!郭艾伦满血回归,赛后再获喜讯接连战胜浙江、吉林2个强敌,辽宁队表现强势回暖,也展现出了自己的强劲实力,接下来,辽宁又迎来了福建队的挑战,面对这个对手,辽宁也全力争胜。福建队实力不强,但是在赛前,球队……有机颜料与染料无机颜料之间的异同众所周知,着色剂基本分为有机颜料、染料、无机颜料,有机颜料最近发展迅速。其与其他两种着色剂有何异同呢?有机颜料与染料的异同有机颜料和染料都是有色的有机化合物,从有机颜料与……平谷金海湖镇开通信贷直通车新京报讯(记者曹晶瑞)12月17日,新京报记者获悉,平谷区金海湖镇已开通信贷直通车,将信贷服务送到田间地头。2022年3月,农业农村部发布《农业农村部关于推进农业经营主体……认识自己,成全自己,放过自己意大利画家莫迪里阿尼曾经说过:人最大的劣根性,就是双眼都用来盯着别人,所以我们要用一只眼看世界,留另一只眼来审视自己。真正聪明的人,都会用一只眼冷静地观察世界,用另……不穿内衣就是不害臊吗?女性的胸部和身材还有长相都是一样的,每个都有独特的大小、位置和形状。大家好,今天我们来跟大家分享一个让大家难以启齿却又困惑的问题,就是关于女性穿不穿内衣?要不要穿内……全尺寸纯电真香SUV试驾体验换电先锋睿蓝9当下,新能源汽车市场炙手可热,正在逐步争夺传统燃油车的市场份额。但随着新能源汽车市占率的提高,车辆续航、补能、电池安全一直都是消费者最为关注的问题。如充电桩难找、充电比较慢,找……全网最全Sentinel资源指标数据统计实现源码解析节点选择器:NodeSelectorSlotNodeSelectorSlot负责为资源的首次访问创建DefaultNode,以及维护Context。curNode和调用树……曲线模特NakitendeEsther证明曲线女孩可以搭配任一个胖女人看起来很有吸引力许多大码模特以及服装系列都证明了这一点,这些系列在市场上越来越多,并且是专门为泡芙缝制的。一个个人的例子是,你可以在任何体重下看起来都很棒,那就……9月13日晚间消息多家公司发布重大利空消息,1公司减持超21一、增减持增持苏宁环球:员工持股计划拟增持3。95长缆科技:员工持股计划拟增持2。07中环环保:员工持股计划拟增持1。89美年健康:员工持股计划拟……你是否经常鼻塞流涕?教你一些药浴小方法鼻窦炎会造成患者出现鼻塞流涕的情况,不仅量多不止,还常伴有头痛、头闷、鼻塞、嗅觉减退等症状,严重影响患者的正常工作和生活。在中医学上,鼻窦炎被称为鼻渊,是鼻科常见多发病之……热点解读总是疲惫想睡觉,春困难道是种病?春天总是被人们赋予许多美好的形容词,生机盎然、万物复苏。春天是一个充满希望的季节,古人说春天恰是读书天不是没有道理的。但是春光再明媚,还是有不少人陷入了春困的烦恼,仿佛再美丽的……
清远笔架山缘舍温泉山庄,特色木屋装饰,320元独享天然山泉泳特斯拉我是怎么把和比亚迪一样的成本卖出宝马价格的?早上喝牛奶更好吗几个喝牛奶的误区要避开光激发的硝基芳烃在烯烃氧化裂解中的作用宇宙中最古老的光,记录宇宙诞生38万年的信息,也决定了人类未碧桂园上半年净利润下跌高达约70,会暴雷吗?给新生儿挤乳头?坑娃操作雪梨里加一把百合,教你从没吃过的做法,营养又美味,比肉还香和尚庙旁有尼姑庵的原因新华全媒山东工业转型升级新观察保护好青海湖智慧物流产业链,千亿空间持续扩容机会在这里(附股)

友情链接:中准网聚热点快百科快传网快生活快软网快好知文好找七猫云易事利