We add the Spring Cloud Sentinel dependency in the project. After the addition, the Spring-cloud-starter – alibaba-Sentinel goes back to initialize the spring.factories during the spring-boot startup The configuration information, such as: SentinelWebAutoConfiguration, SentinelAutoConfiguration configuration file to initialize

Before I go into the code, let me just say that my version number is Sentinel 1.8.0. Everything that follows is based on that version

How it works

The easiest way to configure flow control rules is to use @resouresetinel. This annotation can directly define flow control rules and degrade rules. Here’s a simple example of how to use it:

@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);
}
Copy the code

If you’re familiar with Spring-related components, you can assume that this is mostly through Spring Aop. To intercept the getStockDetails method. Let’s look at SentinelAutoConfiguration configuration file, we can find SentinelResourceAspect Bean definition method.

@Bean
@ConditionalOnMissingBean
public SentinelResourceAspect sentinelResourceAspect(a) {
   return new SentinelResourceAspect();
}
Copy the code

Let’s take a look at how SentinelResourceAspect is processed. The source code is as follows:

/ / define Pointcut
@Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
public void sentinelResourceAnnotationPointcut(a) {}// Around to handle the methods marked with the @sentinelResource annotation
@Around("sentinelResourceAnnotationPointcut()")
public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {
  Method originMethod = resolveMethod(pjp);

  // Get the annotation information
  SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class);

  // Get the resource name
  String resourceName = getResourceName(annotation.value(), originMethod);
  EntryType entryType = annotation.entryType();
  int resourceType = annotation.resourceType();
  Entry entry = null;
  try {
    / / execution entry
    entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
    // Execute the business method
    Object result = pjp.proceed();
    / / return
    return result;
  } catch (BlockException ex) {
    / / BlockException processing
    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);
      // Handle the degradation
      return handleFallback(pjp, annotation, ex);
    }

    // No fallback function can handle the exception, so throw it out.
    throwex; }}Copy the code

To summarize, the execution of @SentinelResource starts with Aop interception, then the corresponding flow control rules are executed through sphu.entry, and finally the invocation of business methods. If the flow control rule is triggered, the BlockException is handled first and then the fallback method is called to determine whether the service is degraded. HandleBlockException, handleFallback processing.

Chain of responsibility mode handles flow control

Through the above comb, we know that for the process of flow control, the core processing method is SphU. Entry. In this method, the main thing is to initialize the flow control Solt and execute Solt. During this process, you will process the following page functions: cluster point definition, flow control, circuit breaker degradation, and system whitelist.

1. Initialize the chain of responsibility

Below is the core code that initializes Solt in SphU. EntryWithPriority

// Delete some code
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
  throws BlockException {

  // Initialize the chain of responsibility
  ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);

  Entry e = new CtEntry(resourceWrapper, chain, context);
  try {
    / / execution entry
    chain.entry(context, resourceWrapper, null, count, prioritized, args);
  } catch (BlockException e1) {
    e.exit(count, args);
    / / exception is thrown, let SentinelResourceAspect invokeResourceWithSentinel unified handling
    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;
}
Copy the code

As I step through the lookProcessChain method, we can see the final chain of responsibility initializer class, which defaults to DefaultSlotChainBuilder

public class DefaultSlotChainBuilder implements SlotChainBuilder {

    @Override
    public ProcessorSlotChain build(a) {
        ProcessorSlotChain chain = new DefaultProcessorSlotChain();

        // Note: the instances of ProcessorSlot should be different, since they are not stateless.
        // Load all ProcessorSlot implementations via SPI, sorted by Order
        List<ProcessorSlot> sortedSlotList = SpiLoader.loadPrototypeInstanceListSorted(ProcessorSlot.class);
        for (ProcessorSlot slot : sortedSlotList) {
            if(! (slotinstanceof AbstractLinkedProcessorSlot)) {
                RecordLog.warn("The ProcessorSlot(" + slot.getClass().getCanonicalName() + ") is not an instance of AbstractLinkedProcessorSlot, can't be added into ProcessorSlotChain");
                continue;
            }
					  // Add to the end of chainchain.addLast((AbstractLinkedProcessorSlot<? >) slot); }returnchain; }}Copy the code

2. Process of chain of responsibility

We can use breakpoints to see the order of all solts in the sortedSlotList collection as shown in the figure below

We can make a simple analysis one by one in the following order

  1. NodeSelectorSolt
  2. CusterBuilderSolt
  3. LogSlot
  4. StatisicSlot
  5. AuthoritySolt
  6. SystemSolts
  7. ParamFlowSolt
  8. FlowSolt
  9. DegradeSlot

For the Slot flow control cooperation process of Sentinel, please refer to the official documents, as shown in the figure below:

FlowSolt flow control

Through NodeSelectorSolt, CusterBuilderSolt, StatisicSlot and a series of request data processing, FlowSolt will enter the flow control rules, and all Solts will execute the entry method, as shown below

// Entry method of FlowSolt
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                  boolean prioritized, Object... args) throws Throwable {
  // Check the traffic
  checkFlow(resourceWrapper, context, node, count, prioritized);

  fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
Copy the code

In the subsequent process, the specific flow control policy is judged. The default is fast failure, and the DefaultController method is executed.

// DefaultController
@Override
public boolean canPass(Node node, int acquireCount, boolean prioritized) {
  // The number of times the current resource was called
  int curCount = avgUsedTokens(node);
  // The number of current resource calls + 1 > the current threshold
  if (curCount + acquireCount > count) {
    // Delete the score code
    / / not through
    return false;
  }
  / / by
  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());
}
Copy the code

If the above return fails to return, then a FlowException is thrown

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)) {// If the flow control rule does not pass, a FlowException is thrown
        throw newFlowException(rule.getLimitApp(), rule); }}}}Copy the code

Then statistics are added to StatisticSlot, and finally thrown to SentinelResourceAspect for processing to complete flow control. If it is a BlockException, it will go to the handleBlockException method. If it is any other business exception, it will first check whether fallback processing is configured. If so, If handleFallback is not called, the flow control function is completed

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;
}
Copy the code

DegradeSlot relegation

A circuit breaker is triggered when certain resources are constantly faulty. Instead of continuing access to a resource that has failed, the breaker intercepts the request and returns a fault signal.

Sentinel in DegradeSlot realized the function of fusing the drop in this Slot, it has three state OPEN, HALF_OPEN, CLOSED ResponseTimeCircuitBreaker RT response time dimension to analysis, The process of circuit breaker operation. Here is the workflow for a standard circuit breaker:

The source process of Sentinel implementation is shown in the figure below:

Sentinel via a Web interceptor

Sentinel by default does not use @Resourcesentinel annotations to implement flow control. Sentinel uses interceptors to implement flow control. Initialize the class in SentinelWebAutoConfiguration it implements WebMvcConfigurer interface, Initialize beans such as sentinelWebInterceptor in the sentinelWebInterceptor method.

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

We handle this in the SentinelWebInterceptor’s core method, preHandle, where we see the familiar chain of responsibility for procedure call flow control in sphu.entry. Because the logic is similar, I won’t go into more detail here. The code is as follows:

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; }}Copy the code

Reference documentation

Github.com/alibaba/Sen…

Martinfowler.com/bliki/Circu…