一篇带给你Sentinel 流控原理

2025-05-29 0 26

一篇带给你Sentinel 流控原理

我们在项目中添加 Spring Cloud Sentinel 依赖添加后 spring-cloud-starter-alibaba-sentinel 在 Spring-Boot 启动的过程中回去初始化 spring.factories 中的配置信息,如:SentinelWebAutoConfiguration 、SentinelAutoConfiguration 等配置文件来初始化

再讲代码之前我先声明一下我的版本号sentinel 1.8.0 。后续的所有内容均基于该版本进行

@ResoureSetinel 工作原理

配置流控规则我们最简单的方式就是通过 @ResoureSetinel 的方式来管理,该注解可以直接定义流控规则、降级规则。下面是一个简单的使用例子:

  1. @SentinelResource(value="ResOrderGet",
  2. fallback="fallback",
  3. fallbackClass=SentinelResourceExceptionHandler.class,
  4. blockHandler="blockHandler",
  5. blockHandlerClass=SentinelResourceExceptionHandler.class
  6. )
  7. @GetMapping("/order/get/{id}")
  8. publicCommonResult<StockModel>getStockDetails(@PathVariableIntegerid){
  9. StockModelstockModel=newStockModel();
  10. stockModel.setCode("STOCK==>1000");
  11. stockModel.setId(id);
  12. returnCommonResult.success(stockModel);
  13. }

如果大家熟悉 Spring 相关的组件大家都可以想到,这里多半是通过Spring Aop. 的方式来拦截 getStockDetails 方法。我们先看看SentinelAutoConfiguration 配置文件,我们可以找到 SentinelResourceAspect Bean 的定义方法。

  1. @Bean
  2. @ConditionalOnMissingBean
  3. publicSentinelResourceAspectsentinelResourceAspect(){
  4. returnnewSentinelResourceAspect();
  5. }

让后我们再来看看 SentinelResourceAspect 具体是怎么处理的,源码如下:

  1. //定义Pointcut
  2. @Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
  3. publicvoidsentinelResourceAnnotationPointcut(){
  4. }
  5. //Around来对被标记@SentinelResource注解的方法进行处理
  6. @Around("sentinelResourceAnnotationPointcut()")
  7. publicObjectinvokeResourceWithSentinel(ProceedingJoinPointpjp)throwsThrowable{
  8. MethodoriginMethod=resolveMethod(pjp);
  9. //获取注解信息
  10. SentinelResourceannotation=originMethod.getAnnotation(SentinelResource.class);
  11. //获取资源名称
  12. StringresourceName=getResourceName(annotation.value(),originMethod);
  13. EntryTypeentryType=annotation.entryType();
  14. intresourceType=annotation.resourceType();
  15. Entryentry=null;
  16. try{
  17. //执行entry
  18. entry=SphU.entry(resourceName,resourceType,entryType,pjp.getArgs());
  19. //执行业务方法
  20. Objectresult=pjp.proceed();
  21. //返回
  22. returnresult;
  23. }catch(BlockExceptionex){
  24. //处理BlockException
  25. returnhandleBlockException(pjp,annotation,ex);
  26. }catch(Throwableex){
  27. Class<?extendsThrowable>[]exceptionsToIgnore=annotation.exceptionsToIgnore();
  28. //Theignorelistwillbecheckedfirst.
  29. if(exceptionsToIgnore.length>0&&exceptionBelongsTo(ex,exceptionsToIgnore)){
  30. throwex;
  31. }
  32. if(exceptionBelongsTo(ex,annotation.exceptionsToTrace())){
  33. traceException(ex);
  34. //处理降级
  35. returnhandleFallback(pjp,annotation,ex);
  36. }
  37. //Nofallbackfunctioncanhandletheexception,sothrowitout.
  38. throwex;
  39. }
  40. }

我们总结一下, @SentinelResource 的执行过程, 首先是通过 Aop 进行拦截,然后通过 SphU.entry 执行对应的流控规则,最后调用业务方法。如果触发流控规则首先处理流控异常 BlockException 然后在判断是否有服务降级的处理,如果有就调用 fallback 方法。通过 handleBlockException 、handleFallback 进行处理。

责任链模式处理流控

通过上面的梳理,我们知道对于流控的过程,核心处理方法就是 SphU.entry 。在这个方法中其实主要就是初始化流控 Solt 和执行 Solt. 在这个过程中会对:簇点定义、流量控制、熔断降级、系统白名单等页面功能进行处理。

1. 初始化责任链

下面是初始化 Solt 的核心代码在 SphU.entryWithPriority

  1. //删减部分代码
  2. privateEntryentryWithPriority(ResourceWrapperresourceWrapper,intcount,booleanprioritized,Object…args)
  3. throwsBlockException{
  4. //初始化责任链
  5. ProcessorSlot<Object>chain=lookProcessChain(resourceWrapper);
  6. Entrye=newCtEntry(resourceWrapper,chain,context);
  7. try{
  8. //执行entry
  9. chain.entry(context,resourceWrapper,null,count,prioritized,args);
  10. }catch(BlockExceptione1){
  11. e.exit(count,args);
  12. //异常抛出,让SentinelResourceAspect.invokeResourceWithSentinel统一处理
  13. throwe1;
  14. }catch(Throwablee1){
  15. //Thisshouldnothappen,unlessthereareerrorsexistinginSentinelinternal.
  16. RecordLog.info("Sentinelunexpectedexception",e1);
  17. }
  18. returne;
  19. }

通过 lookProcessChain 方法我逐步的查找,我们可以看到最终的责任链初始化类,默认是 DefaultSlotChainBuilder

  1. publicclassDefaultSlotChainBuilderimplementsSlotChainBuilder{
  2. @Override
  3. publicProcessorSlotChainbuild(){
  4. ProcessorSlotChainchain=newDefaultProcessorSlotChain();
  5. //Note:theinstancesofProcessorSlotshouldbedifferent,sincetheyarenotstateless.
  6. //通过SPI去加载所有的ProcessorSlot实现,通过Order排序
  7. List<ProcessorSlot>sortedSlotList=SpiLoader.loadPrototypeInstanceListSorted(ProcessorSlot.class);
  8. for(ProcessorSlotslot:sortedSlotList){
  9. if(!(slotinstanceofAbstractLinkedProcessorSlot)){
  10. RecordLog.warn("TheProcessorSlot("+slot.getClass().getCanonicalName()+")isnotaninstanceofAbstractLinkedProcessorSlot,can'tbeaddedintoProcessorSlotChain");
  11. continue;
  12. }
  13. //添加到chain尾部
  14. chain.addLast((AbstractLinkedProcessorSlot<?>)slot);
  15. }
  16. returnchain;
  17. }
  18. }

2. 责任链的处理过程

我们可以通过断点的方式来查看在 sortedSlotList 集合中所有的 solt 顺序如下图所示:

一篇带给你Sentinel 流控原理

我们可以通过如下的顺序进行逐个的简单的分析一下

  • NodeSelectorSolt
  • CusterBuilderSolt
  • LogSlot
  • StatisicSlot
  • AuthoritySolt
  • SystemSolts
  • ParamFlowSolt
  • FlowSolt
  • DegradeSlot

对于 Sentinel 的 Slot 流控协作流程可以参考官方给出的文档, 如下图所示:

一篇带给你Sentinel 流控原理

FlowSolt 流控

通过 NodeSelectorSolt、CusterBuilderSolt、StatisicSlot 等一系列的请求数据处理,在 FlowSolt会进入流控规则,所有的 Solt 都会执行 entry 方法, 如下所示

  1. //FlowSolt的entry方法
  2. @Override
  3. publicvoidentry(Contextcontext,ResourceWrapperresourceWrapper,DefaultNodenode,intcount,
  4. booleanprioritized,Object…args)throwsThrowable{
  5. //检查流量
  6. checkFlow(resourceWrapper,context,node,count,prioritized);
  7. fireEntry(context,resourceWrapper,node,count,prioritized,args);
  8. }

在后续的流程中,会执进行判断具体的流控策略,默认是快速失败,会执行 DefaultController 方法。

  1. //DefaultController
  2. @Override
  3. publicbooleancanPass(Nodenode,intacquireCount,booleanprioritized){
  4. //当前资源的调用次数
  5. intcurCount=avgUsedTokens(node);
  6. //当前资源的调用次数+1>当前阈值
  7. if(curCount+acquireCount>count){
  8. //删减比分代码
  9. //不通过
  10. returnfalse;
  11. }
  12. //通过
  13. returntrue;
  14. }
  15. privateintavgUsedTokens(Nodenode){
  16. if(node==null){
  17. returnDEFAULT_AVG_USED_TOKENS;
  18. }
  19. returngrade==RuleConstant.FLOW_GRADE_THREAD?node.curThreadNum():(int)(node.passQps());
  20. }

如果上面返回不通过会回到,那么会抛出 FlowException

  1. publicvoidcheckFlow(Function<String,Collection<FlowRule>>ruleProvider,ResourceWrapperresource,
  2. Contextcontext,DefaultNodenode,intcount,booleanprioritized)throwsBlockException{
  3. if(ruleProvider==null||resource==null){
  4. return;
  5. }
  6. Collection<FlowRule>rules=ruleProvider.apply(resource.getName());
  7. if(rules!=null){
  8. for(FlowRulerule:rules){
  9. if(!canPassCheck(rule,context,node,count,prioritized)){
  10. //流控规则不通过,会抛出FlowException
  11. thrownewFlowException(rule.getLimitApp(),rule);
  12. }
  13. }
  14. }
  15. }

然后会在 StatisticSlot 中增加统计信息, 最后会抛出给 SentinelResourceAspect 进行处理,完成流控功能。我们再来看看这个异常信息,如果是BlockException 异常,会进入 handleBlockException 方法处理, 如果是其他的业务异常首先会判断是否有配置 fallback 处理如果有,就调用 handleFallback 没有就继续往外抛,至此完成流控功能

  1. try{
  2. entry=SphU.entry(resourceName,resourceType,entryType,pjp.getArgs());
  3. Objectresult=pjp.proceed();
  4. returnresult;
  5. }catch(BlockExceptionex){
  6. returnhandleBlockException(pjp,annotation,ex);
  7. }catch(Throwableex){
  8. Class<?extendsThrowable>[]exceptionsToIgnore=annotation.exceptionsToIgnore();
  9. //Theignorelistwillbecheckedfirst.
  10. if(exceptionsToIgnore.length>0&&exceptionBelongsTo(ex,exceptionsToIgnore)){
  11. throwex;
  12. }
  13. if(exceptionBelongsTo(ex,annotation.exceptionsToTrace())){
  14. traceException(ex);
  15. returnhandleFallback(pjp,annotation,ex);
  16. }
  17. //Nofallbackfunctioncanhandletheexception,sothrowitout.
  18. throwex;
  19. }

DegradeSlot 降级

断路器的作用是当某些资源一直出现故障时,将触发断路器。断路器不会继续访问已经发生故障的资源,而是拦截请求并返回故障信号。

Sentinel 在 DegradeSlot 这个 Slot 中实现了熔断降级的功能,它有三个状态 OPEN 、HALF_OPEN、CLOSED 以ResponseTimeCircuitBreaker RT 响应时间维度来分析, 断路器工作的过程。下面是一个标准断路器的工作流程:

一篇带给你Sentinel 流控原理

Sentinel 实现的源码过程如下图所示:

一篇带给你Sentinel 流控原理

Sentinel 通过 Web 拦截器

Sentinel 在默认情况下, 不使用 @ResourceSentinel 注解实现流控的时候, Sentinel 通过拦截器进行流控实现的。初始化类在 SentinelWebAutoConfiguration 它实现了 WebMvcConfigurer 接口,在 sentinelWebInterceptor 方法初始化 SentinelWebInterceptor 等 Bean。

  1. @Bean
  2. @ConditionalOnProperty(name="spring.cloud.sentinel.filter.enabled",
  3. matchIfMissing=true)
  4. publicSentinelWebInterceptorsentinelWebInterceptor(
  5. SentinelWebMvcConfigsentinelWebMvcConfig){
  6. returnnewSentinelWebInterceptor(sentinelWebMvcConfig);
  7. }

我们在 SentinelWebInterceptor 的核心方法 preHandle 中处理,这里面我们又可以看到 SphU.entry 熟悉的过程调用流控的责任链。由于逻辑都类似,此处不再多说。代码如下:

  1. publicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)
  2. throwsException{
  3. try{
  4. StringresourceName=getResourceName(request);
  5. if(StringUtil.isEmpty(resourceName)){
  6. returntrue;
  7. }
  8. if(increaseReferece(request,this.baseWebMvcConfig.getRequestRefName(),1)!=1){
  9. returntrue;
  10. }
  11. //Parsetherequestoriginusingregisteredoriginparser.
  12. Stringorigin=parseOrigin(request);
  13. StringcontextName=getContextName(request);
  14. ContextUtil.enter(contextName,origin);
  15. Entryentry=SphU.entry(resourceName,ResourceTypeConstants.COMMON_WEB,EntryType.IN);
  16. request.setAttribute(baseWebMvcConfig.getRequestAttributeName(),entry);
  17. returntrue;
  18. }catch(BlockExceptione){
  19. try{
  20. handleBlockException(request,response,e);
  21. }finally{
  22. ContextUtil.exit();
  23. }
  24. returnfalse;
  25. }
  26. }

参考文档

https://github.com/alibaba/Sentinel/wiki

https://martinfowler.com/bliki/CircuitBreaker.html

原文链接:https://mp.weixin.qq.com/s/88ZnMVwvMKVk_uWTOvK-qg

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

快网idc优惠网 建站教程 一篇带给你Sentinel 流控原理 https://www.kuaiidc.com/111375.html

相关文章

发表评论
暂无评论