温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Sentinel 流控的原理是什么

发布时间:2021-06-18 14:43:38 来源:亿速云 阅读:200 作者:Leah 栏目:web开发

Sentinel 流控的原理是什么,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。

@ResoureSetinel 工作原理

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

@SentinelResource(value = "ResOrderGet",                   fallback = "fallback",                   fallbackClass = SentinelResourceExceptionHandler.class,                   blockHandler = "blockHandler",                   blockHandlerClass = SentinelResourceExceptionHandler.class                  ) @GetMapping("/order/get/{id}") public CommonResult<StockModel> getStockDetails(@PathVariable Integer id) {   StockModel stockModel = new StockModel();   stockModel.setCode("STOCK==>1000");   stockModel.setId(id);   return CommonResult.success(stockModel); }

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

@Bean @ConditionalOnMissingBean public SentinelResourceAspect sentinelResourceAspect() {    return new SentinelResourceAspect(); }

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

// 定义 Pointcut @Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)") public void sentinelResourceAnnotationPointcut() { } // Around 来对被标记 @SentinelResource 注解的方法进行处理 @Around("sentinelResourceAnnotationPointcut()") public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {   Method originMethod = resolveMethod(pjp);   // 获取注解信息   SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class);   // 获取资源名称   String resourceName = getResourceName(annotation.value(), originMethod);   EntryType entryType = annotation.entryType();   int resourceType = annotation.resourceType();   Entry entry = null;   try {     // 执行 entry     entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());     // 执行业务方法     Object result = pjp.proceed();     // 返回     return result;   } catch (BlockException ex) {     // 处理 BlockException     return handleBlockException(pjp, annotation, ex);   } catch (Throwable ex) {     Class<? extends Throwable>[] exceptionsToIgnore = annotation.exceptionsToIgnore();     // The ignore list will be checked first.     if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) {       throw ex;     }     if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) {       traceException(ex);       // 处理降级       return handleFallback(pjp, annotation, ex);     }     // No fallback function can handle the exception, so throw it out.     throw ex;   } }

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

责任链模式处理流控

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

1. 初始化责任链

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

// 删减部分代码 private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)   throws BlockException {   // 初始化责任链   ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);   Entry e = new CtEntry(resourceWrapper, chain, context);   try {     // 执行 entry     chain.entry(context, resourceWrapper, null, count, prioritized, args);   } catch (BlockException e1) {     e.exit(count, args);     // 异常抛出,让 SentinelResourceAspect.invokeResourceWithSentinel 统一处理     throw e1;   } catch (Throwable e1) {     // This should not happen, unless there are errors existing in Sentinel internal.     RecordLog.info("Sentinel unexpected exception", e1);   }   return e; }

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

public class DefaultSlotChainBuilder implements SlotChainBuilder {     @Override     public ProcessorSlotChain build() {         ProcessorSlotChain chain = new DefaultProcessorSlotChain();         // Note: the instances of ProcessorSlot should be different, since they are not stateless.         // 通过 SPI 去加载所有的  ProcessorSlot 实现,通过 Order 排序         List<ProcessorSlot> sortedSlotList = SpiLoader.loadPrototypeInstanceListSorted(ProcessorSlot.class);         for (ProcessorSlot slot : sortedSlotList) {             if (!(slot instanceof AbstractLinkedProcessorSlot)) {                 RecordLog.warn("The ProcessorSlot(" + slot.getClass().getCanonicalName() + ") is not an instance of AbstractLinkedProcessorSlot, can't be added into ProcessorSlotChain");                 continue;             }                       // 添加到 chain 尾部             chain.addLast((AbstractLinkedProcessorSlot<?>) slot);         }         return chain;     } }

2. 责任链的处理过程

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

Sentinel 流控的原理是什么

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

  • NodeSelectorSolt

  • CusterBuilderSolt

  • LogSlot

  • StatisicSlot

  • AuthoritySolt

  • SystemSolts

  • ParamFlowSolt

  • FlowSolt

  • DegradeSlot

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

Sentinel 流控的原理是什么

FlowSolt 流控

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

// FlowSolt 的 entry 方法 @Override public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,                   boolean prioritized, Object... args) throws Throwable {   // 检查流量   checkFlow(resourceWrapper, context, node, count, prioritized);   fireEntry(context, resourceWrapper, node, count, prioritized, args); }

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

// DefaultController @Override public boolean canPass(Node node, int acquireCount, boolean prioritized) {   // 当前资源的调用次数   int curCount = avgUsedTokens(node);   // 当前资源的调用次数 + 1 > 当前阈值   if (curCount + acquireCount > count) {     // 删减比分代码     // 不通过     return false;   }   // 通过   return true; } private int avgUsedTokens(Node node) {   if (node == null) {     return DEFAULT_AVG_USED_TOKENS;   }   return grade == RuleConstant.FLOW_GRADE_THREAD ? node.curThreadNum() : (int)(node.passQps()); }

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

public void checkFlow(Function<String, Collection<FlowRule>> ruleProvider, ResourceWrapper resource,                       Context context, DefaultNode node, int count, boolean prioritized) throws BlockException {   if (ruleProvider == null || resource == null) {     return;   }   Collection<FlowRule> rules = ruleProvider.apply(resource.getName());   if (rules != null) {     for (FlowRule rule : rules) {       if (!canPassCheck(rule, context, node, count, prioritized)) {         // 流控规则不通过,会抛出 FlowException         throw new FlowException(rule.getLimitApp(), rule);       }     }   } }

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

try {   entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());   Object result = pjp.proceed();   return result; } catch (BlockException ex) {   return handleBlockException(pjp, annotation, ex); } catch (Throwable ex) {   Class<? extends Throwable>[] exceptionsToIgnore = annotation.exceptionsToIgnore();   // The ignore list will be checked first.   if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) {     throw ex;   }   if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) {     traceException(ex);     return handleFallback(pjp, annotation, ex);   }   // No fallback function can handle the exception, so throw it out.   throw ex; }

DegradeSlot 降级

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

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

Sentinel 流控的原理是什么

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

Sentinel 流控的原理是什么

Sentinel 通过 Web  拦截器

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

@Bean @ConditionalOnProperty(name = "spring.cloud.sentinel.filter.enabled",                        matchIfMissing = true) public SentinelWebInterceptor sentinelWebInterceptor(   SentinelWebMvcConfig sentinelWebMvcConfig) {   return new SentinelWebInterceptor(sentinelWebMvcConfig); }

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

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)   throws Exception {   try {     String resourceName = getResourceName(request);     if (StringUtil.isEmpty(resourceName)) {       return true;     }     if (increaseReferece(request, this.baseWebMvcConfig.getRequestRefName(), 1) != 1) {       return true;     }     // Parse the request origin using registered origin parser.     String origin = parseOrigin(request);     String contextName = getContextName(request);     ContextUtil.enter(contextName, origin);     Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN);     request.setAttribute(baseWebMvcConfig.getRequestAttributeName(), entry);     return true;   } catch (BlockException e) {     try {       handleBlockException(request, response, e);     } finally {       ContextUtil.exit();     }     return false;   } }

看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注亿速云行业资讯频道,感谢您对亿速云的支持。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI