preface

This chapter learns source code of relevant modules adapted with Sentinel and other frameworks, including:

  1. AspectJ: Implement the Sentinel core API in an AOP manner;
  2. SpringMVC: intercepts the Controller method for traffic protection;
  3. Dubbo: Intercepts service interfaces and methods (both caller and server) for traffic protection;
  4. SpringCloud: Sentinel for RestTemplate and Feign;

A, AspectJ

Sentinel’s core API does operations before and after resource methods, including counting traffic, checking rules, logging exceptions, and so on.

The Sentinel-annotation-AspectJ module uses AspectJ to provide sentinel support for AOP.

The usage is as follows:

Step1: inject the section.

@Configuration
public class AopConfiguration {

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

Step2: use SentinelResource annotations on the target resource method. This will execute the sphu. entry method before the target method is executed, and the entry. Exit method after the target method is executed, and the corresponding Exception handling class will be invoked when an Exception occurs.

@Override
@SentinelResource(value = "hello", fallback = "helloFallback")
public String hello(long s) {
    if (s < 0) {
        throw new IllegalArgumentException("invalid arg");
    }
    return String.format("Hello at %d", s);
}
Copy the code

1. SentinelResource Notes

@Target({ElementType.METHOD, Elementtype. TYPE}) @Retention(retentionPolicy.runtime) @Inherited Public @Interface SentinelResource {// Resource name String value() default ""; // Flow direction, IN inlet, OUT outlet, default outlet. If you want to use system rules, remember to change to IN. EntryType entryType() default EntryType.OUT; // resourceType 0- common 1-web (HTTP) 2- RPC 3-gateway int resourceType() default 0; Block Exception String blockHandler() default ""; Class<? >[] blockHandlerClass() default {}; // Handle non-block Exception String fallback() default ""; String defaultFallback() default ""; Class<? >[] fallbackClass() default {}; // Which exceptions need to be set into Entry, which are used for error rate, error count, and degradation rule Class<? extends Throwable>[] exceptionsToTrace() default {Throwable.class}; Class<? extends Throwable>[] exceptionsToIgnore() default {}; }Copy the code

The SentinelResource annotation contains three properties:

  1. For SphU. Entry method: value- resource name; EntryType – Traffic direction; ResourceType – resourceType;
  2. BlockException handling: blockHandler- Handles BlockException; BlockHandlerClass – By default, blockHandler must be in this class. If not, you can configure blockHandlerClass in another class, but it must be a static method.
  3. Non-blockexception handling: exceptionsToTrace- Which exceptions need to be handled; ExceptionsToIgnore – Which exceptions do not need to be handled; Fallback method; DefaultFallback – Default degrade method; FallbackClass – By default, degraded methods must be in this class. If not, you can configure fallbackClass to be in another class, but it must be static.

2, SentinelResourceAspect

SentinelResourceAspect defines the method by which Pointcut is annotated for all Sentinelresources.

@Aspect public class SentinelResourceAspect extends AbstractSentinelAspectSupport { @Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)") public void sentinelResourceAnnotationPointcut() { } @Around("sentinelResourceAnnotationPointcut()") public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable { Method originMethod = resolveMethod(pjp); SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class); if (annotation == null) { throw new IllegalStateException("Wrong state for SentinelResource annotation"); } // resourceName special logic 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) {// Handle Block exceptions return handleBlockException(PJP, annotation, ex); } catch (Throwable ex) {Class<? extends Throwable>[] exceptionsToIgnore = annotation.exceptionsToIgnore(); if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) { throw ex; } if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) { traceException(ex); return handleFallback(pjp, annotation, ex); } throw ex; } finally {// Exit if (entry! = null) { entry.exit(1, pjp.getArgs()); }}}}Copy the code

Surrounding the method invokeResourceWithSentinel pay attention to several points:

  1. GetResourceName Obtains the resource name.
  2. HandleBlockException handles BlockException;
  3. Handle other exceptions;

The resource name takes precedence over the value attribute on the annotation, or if the user has not configured it, the class + method signature is used as the resource name.

Such as com. Alibaba. CSP. Sentinel. Util. MethodUtil: resolveMethodName (Java. Lang. Reflect. Method)

// AbstractSentinelAspectSupport.java
protected String getResourceName(String resourceName, Method method) {
    if (StringUtil.isNotBlank(resourceName)) {
        return resourceName;
    }
    return MethodUtil.resolveMethodName(method);
}
Copy the code

The handleBlockException method handles BlockException.

// AbstractSentinelAspectSupport.java protected Object handleBlockException(ProceedingJoinPoint pjp, SentinelResource annotation, BlockException ex) throws Throwable { // 1. Find Method blockHandlerMethod = extractBlockHandlerMethod (PJP, the annotation blockHandler (), annotation.blockHandlerClass()); if (blockHandlerMethod ! BlockException Object[] originArgs = pjp.getargs (); Object[] args = Arrays.copyOf(originArgs, originArgs.length + 1); args[args.length - 1] = ex; Try {/ / 3. The execution method if (isStatic (blockHandlerMethod)) {return blockHandlerMethod. Invoke (null, args); } return blockHandlerMethod.invoke(pjp.getTarget(), args); } catch (InvocationTargetException e) { throw e.getTargetException(); If blockHandler is not found, return handleFallback(PJP, annotation, ex) with fallback; }Copy the code

First find the BlockException handler based on the blockHandler and blockHandlerClass in the annotations. If blockHandler is not found, no exception is thrown and fallback is executed.

// AbstractSentinelAspectSupport.java private Method extractBlockHandlerMethod(ProceedingJoinPoint pjp, String name, Class<? >[] locationClass) { if (StringUtil.isBlank(name)) { return null; } boolean mustStatic = locationClass ! = null && locationClass.length >= 1; Class<? > clazz; Clazz = locationClass[0]; if (mustStatic) {// If blockHandlerClass is configured, clazz = locationClass[0]; } else {// otherwise take the current class force blockHandler clazz = pjp.gettarget ().getClass(); } / / ResourceMetadataRegistry cache blockHandler MethodWrapper m = ResourceMetadataRegistry lookupBlockHandler (clazz, name); If (m = = null) {/ / handler parsed out goals, will recursively to the parent class to find Method Method = resolveBlockHandlerInternal (PJP, name clazz, mustStatic); / / on the cache ResourceMetadataRegistry. UpdateBlockHandlerFor (clazz, name, method); return method; } if (! m.isPresent()) { return null; } return m.getMethod(); }Copy the code

For non-blockExceptions, the Throwable is filtered by the exceptionsToIgnore and exceptionsToTrace attributes in the annotations. By default, all exceptions are processed.

/ / SentinelResourceAspect. Java / / / / invokeResourceWithSentinel method with other exception Class <? extends Throwable>[] exceptionsToIgnore = annotation.exceptionsToIgnore(); if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) { throw ex; } if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) { traceException(ex); return handleFallback(pjp, annotation, ex); }Copy the code

The traceException method records exceptions to the current Entry. StatisticSlot statistics the error rate and number of errors for a system rule, and generalizes the error rate and number of errors for degrading rules at exit.

// AbstractSentinelAspectSupport.java
protected void traceException(Throwable ex) {
  Tracer.trace(ex);
}
Copy the code

The handleFallback method selects the fallback method in the annotation first, followed by the defaultFallback method. If none is found, the original method is thrown.

// AbstractSentinelAspectSupport.java protected Object handleFallback(ProceedingJoinPoint pjp, String fallback, String defaultFallback, Class<? >[] fallbackClass, Throwable ex) throws Throwable { Object[] originArgs = pjp.getArgs(); // LEVEL1: extractFallbackMethod fallbackMethod = extractFallbackMethod(PJP, fallback, fallbackClass); if (fallbackMethod ! = null) {/ / add the Exception argument list int paramCount = fallbackMethod. GetParameterTypes (.) length; Object[] args; if (paramCount == originArgs.length) { args = originArgs; } else { args = Arrays.copyOf(originArgs, originArgs.length + 1); args[args.length - 1] = ex; } // Invoke a try {if (isStatic(fallbackMethod)) {return fallbackmethod. invoke(null, args); } return fallbackMethod.invoke(pjp.getTarget(), args); } catch (InvocationTargetException e) { throw e.getTargetException(); } } // LEVEL2 : Return handleDefaultFallback(PJP, defaultFallback, fallbackClass, ex); return handleDefaultFallback(PJP, defaultFallback, fallbackClass, ex) }Copy the code

Second, for SpringMVC

Sentinel integration with SpringMVC makes use of SpringMVC’s HandlerInterceptor extension point, intercepting all Controller methods at doDispatch of DispatcherServlet.

Sentinel officially provides the case InterceptorConfig configuration to be used with the SentinelWebInterceptor interceptor for SpringMVC.

@Configuration public class InterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { addSpringMvcInterceptor(registry); } private void addSpringMvcInterceptor(InterceptorRegistry registry) { SentinelWebMvcConfig config = new SentinelWebMvcConfig(); / / processing BlockException config. SetBlockExceptionHandler (new DefaultBlockExceptionHandler ()); / / resource name whether take the Http Method to distinguish the config setHttpMethodSpecify (true); / / whether the context, if distinguish, each resource corresponding to a context config. SetWebContextUnify (true); SetOriginParser (request -> request.getHeader(" s-user "))); AddInterceptor (new SentinelWebInterceptor(config)).addPathPatterns("/**"); }}Copy the code

AbstractSentinelInterceptor achieved all intercept logic.

public abstract class AbstractSentinelInterceptor implements HandlerInterceptor {
}
Copy the code

Before executing the Controller method, execute the preHandle method.

// AbstractSentinelInterceptor.java @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { try { // 1. String resourceName = getResourceName(request); if (StringUtil.isEmpty(resourceName)) { return true; } / / 2. For forward requests for springMVC ignore the if (increaseReferece (request, enclosing baseWebMvcConfig. GetRequestRefName (), 1)! = 1) { return true; String origin = parseOrigin(request); Default sentinel_spring_web_context String contextName = getContextName(request); // 5. Contextutil. enter(contextName, origin); / / 6. Entry rules checking entry entry = SphU. Entry (resourceName, ResourceTypeConstants.COM MON_WEB EntryType. IN); / / 7. In the request. The attribute passed in the Entry request. The setAttribute (baseWebMvcConfig. GetRequestAttributeName (), Entry). return true; } catch (BlockException e) {// 8. Block {handleBlockException(request, response, e); } finally { ContextUtil.exit(); } return false; }}Copy the code

1. Obtain the resource name according to request

This step has different implementations depending on subclasses.

The SentinelWebInterceptor defaults to using urls in spring MVC requestMapping as resources.

public class SentinelWebInterceptor extends AbstractSentinelInterceptor { private final SentinelWebMvcConfig config; @override protected String getResourceName(HttpServletRequest Request) {// Get url Object resourceNameObject = request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE); if (resourceNameObject == null || ! (resourceNameObject instanceof String)) { return null; } String resourceName = (String) resourceNameObject; UrlCleaner = config.geturlcleaner (); if (urlCleaner ! = null) { resourceName = urlCleaner.clean(resourceName); } // If the configuration requires that the request method type be distinguished, Returned as the GET/hello: if (StringUtil isNotEmpty (resourceName) && config. IsHttpMethodSpecify ()) {resourceName = request.getMethod().toUpperCase() + ":" + resourceName; } // Otherwise return the spring Web URL, such as /hello return resourceName; }}Copy the code

In addition, users can implement the UrlCleaner interface for secondary processing of URLS. For example, change /hello/{id} to /hello.

public interface UrlCleaner {
    String clean(String originUrl);
}
Copy the code

If the same resource can use multiple Http Method requests at the same time, such as POST and GET, you can configure httpMethodSpecify to distinguish the same resource with different request methods, such as GET:/ Hello.

SentinelWebTotalInterceptor implementation class, static configuration as a resource name. The Sentinel interceptor is a global interceptor for Spring Web, so the resource name is static.

public class SentinelWebTotalInterceptor extends AbstractSentinelInterceptor { private final SentinelWebMvcTotalConfig config; @Override protected String getResourceName(HttpServletRequest request) { return config.getTotalResourceName(); }}Copy the code

2, release forward request

According to the Forward feature of Spring MVC, Sentinel only performs rule verification and traffic statistics on the first request resource.

IncreaseReferece method is to determine the same client request it, whether through AbstractSentinelInterceptor more than once, if repeat, repeat the request will not do any processing, direct release.

// AbstractSentinelInterceptor.java
private Integer increaseReferece(HttpServletRequest request, String rcKey, int step) {
    Object obj = request.getAttribute(rcKey);
    if (obj == null) {
        obj = Integer.valueOf(0);
    }
    Integer newRc = (Integer)obj + step;
    request.setAttribute(rcKey, newRc);
    return newRc;
}
Copy the code

3. Source

Source requires the user to configure RequestOriginParser and implement it, default origin= empty string.

// AbstractSentinelInterceptor.java protected String parseOrigin(HttpServletRequest request) { String origin = EMPTY_ORIGIN; if (baseWebMvcConfig.getOriginParser() ! = null) { origin = baseWebMvcConfig.getOriginParser().parseOrigin(request); if (StringUtil.isEmpty(origin)) { return EMPTY_ORIGIN; } } return origin; }Copy the code

The RequestOriginParser parseOrigin method parses the Origin from the HttpServletRequest.

public interface RequestOriginParser {
    String parseOrigin(HttpServletRequest request);
}
Copy the code

The official case gets the Origin from the request header.

config.setOriginParser(request -> request.getHeader("S-user"));
Copy the code

4. Get the context name

By default, the context name is sentinel_spring_web_context.

// AbstractSentinelInterceptor.java
protected String getContextName(HttpServletRequest request) {
    // sentinel_spring_web_context
    return SENTINEL_SPRING_WEB_CONTEXT_NAME;
}
Copy the code

If using the SentinelWebInterceptor interceptor, configure webContextUnify=false to support using the resource name as the context name.

This configuration supports the flow control rule whose flow control mode is link. It is useless in other cases.

// SentinelWebInterceptor.java @Override protected String getContextName(HttpServletRequest request) { // The default webContextUnify = true, Sentinel_spring_web_context if (config.iswebContextunify ()) {return super.getContextName(request); } // set webContextUnify=false. Return getResourceName(request); }Copy the code

Nothing special about the next 5-7 steps

5. Handle BlockException

You can implement BlockExceptionHandler and configure it to handle BlockExceptions. Otherwise, the user needs to use Spring’s global exception handling to handle blockExceptions.

// AbstractSentinelInterceptor.java protected void handleBlockException(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception { if (baseWebMvcConfig.getBlockExceptionHandler() ! = null) { baseWebMvcConfig.getBlockExceptionHandler().handle(request, response, e); } else { throw e; } } public interface BlockExceptionHandler { void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception; }Copy the code

The default BlockExceptionHandler implementation is officially provided. The HTTP status code 429 is returned and the response body Blocked by Sentinel (Flow Limiting).

public class DefaultBlockExceptionHandler implements BlockExceptionHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception { response.setStatus(429); PrintWriter out = response.getWriter(); out.print("Blocked by Sentinel (flow limiting)"); out.flush(); out.close(); }}Copy the code

6, exit

The Controller method completes, whether or not an exception is thrown, through the afterCompletion method.

// AbstractSentinelInterceptor.java @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 1. For for springMVC forward request to ignore the if (increaseReferece (request, enclosing baseWebMvcConfig. GetRequestRefName (), 1)! = 0) { return; } / / 2. From the request. The attribute to get Entry Entry Entry = getEntryInRequest (request, baseWebMvcConfig getRequestAttributeName ()); if (entry == null) { // should not happen RecordLog.warn("[{}] No entry found in request, key: {}", getClass().getSimpleName(), baseWebMvcConfig.getRequestAttributeName()); return; } // 3. Record the exception and exit entry traceExceptionAndExit(Entry, ex); // 4. Remove Entry from request.attribute removeEntryInRequest(request); // 5. Contextutil.exit (); }Copy the code

Everything else is easy to understand. Step 3 Tracer. TraceEntry records exceptions to degrade the error rate and number of rules, and finally exits the current Entry.

// AbstractSentinelInterceptor.java protected void traceExceptionAndExit(Entry entry, Exception ex) { if (entry ! = null) { if (ex ! = null) { Tracer.traceEntry(ex, entry); } entry.exit(); }}Copy the code

Third, Dubbo

1, use,

Users only need to import the Sentinel-Apache-Dubbo-Adapter dependency to access Sentinel in Dubbo.

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-apache-dubbo-adapter</artifactId>
    <version>x.y.z</version>
</dependency>
Copy the code

Dubbo’s service interfaces and methods (both the calling side and the server side) automatically become resources in Sentinel.

For interface, resources fully qualified name for the interface, such as com. Alibaba. CSP. The sentinel. Demo. Apache. Dubbo. FooService;

Resources for interface fully qualified name for method: the method signature, such as com. Alibaba. CSP. The sentinel. Demo. Apache. Dubbo. FooService: sayHello (Java. Lang. String).

For providers, you are advised to configure traffic limiting in QPS mode to protect service providers from being overwhelmed by surging traffic.

For consumers, it is recommended to configure flow limiting in the number of threads mode to ensure that they are not affected by unstable services.

The sentinel-Apache-Dubbo-Adapter layer uses the Dubbo SPI to load three filters, which can be configured to disable some sentinel functions.

@Bean public ConsumerConfig consumerConfig() { ConsumerConfig consumerConfig = new ConsumerConfig(); / / disable as a service to the consumer Sentinel function consumerConfig. SetFilter (" - Sentinel. Dubbo. Consumer. Filter "); return consumerConfig; }Copy the code

2, Consumer

The caller has two important filters.

DubboAppContextFilter extracts the application parameter (application name) from DubboURL and saves it as an Attachment to RpcContext, where the Attachment key is dubboApplication.

Attachment passes to the Provider in the interface call.

@Activate(group = CONSUMER) public class DubboAppContextFilter implements Filter { @Override public Result invoke(Invoker<? > invoker, Invocation invocation) throws RpcException { String application = invoker.getUrl().getParameter(CommonConstants.APPLICATION_KEY); if (application ! = null) { RpcContext.getContext().setAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, application); } return invoker.invoke(invocation); }}Copy the code

The core of SentinelDubboConsumerFilter responsible for performing Sentinel API.

@Activate(group = CONSUMER) public class SentinelDubboConsumerFilter extends BaseSentinelDubboFilter { public SentinelDubboConsumerFilter() { RecordLog.info("Sentinel Apache Dubbo consumer filter initialized"); } @Override String getMethodName(Invoker invoker, Invocation invocation, String prefix) { return DubboUtils.getMethodResourceName(invoker, invocation, prefix); } @Override String getInterfaceName(Invoker invoker, String prefix) { return DubboUtils.getInterfaceName(invoker, prefix); } @Override public Result invoke(Invoker<? > invoker, Invocation invocation) throws RpcException { InvokeMode invokeMode = RpcUtils.getInvokeMode(invoker.getUrl(), invocation); if (InvokeMode.SYNC == invokeMode) { return syncInvoke(invoker, invocation); } else { return asyncInvoke(invoker, invocation); }}}Copy the code

SyncInvoke calls synchronously.

// SentinelDubboConsumerFilter.java private Result syncInvoke(Invoker<? > invoker, Invocation invocation) { Entry interfaceEntry = null; Entry methodEntry = null; / / prefix, the default is empty String prefix = DubboAdapterGlobalConfig. GetDubboConsumerResNamePrefixKey (); // interfaceResourceName String interfaceResourceName = getInterfaceName(invoker, prefix); String methodResourceName = getMethodName(Invoker, Invocation, prefix); Try {/ / interface interfaceEntry = SphU. Entry (interfaceResourceName, ResourceTypeConstants.COM MON_RPC EntryType), OUT); / / interface methods methodEntry = SphU. Entry (methodResourceName, ResourceTypeConstants.COM MON_RPC, EntryType OUT, invocation.getArguments()); // Invoke logic (invocation) Result Result = invocation. Invoke (Invocation); If (result.hasException()) {tracer.traceEntry (result.getException(), interfaceEntry); if (result.hasException()) {tracer.traceEntry (result.getException(), interfaceEntry); Tracer.traceEntry(result.getException(), methodEntry); } return result; } catch (BlockException e) { Get DubboFallback interface implementation class to handle BlockException return DubboAdapterGlobalConfig. GetConsumerFallback (). The handle (invoker, invocation, e); } catch (RpcException e) {// Record RPC exceptions for degrading the rule tracer. traceEntry(e, interfaceEntry); Tracer.traceEntry(e, methodEntry); throw e; } finally {// Exit if (methodEntry! = null) { methodEntry.exit(1, invocation.getArguments()); } if (interfaceEntry ! = null) { interfaceEntry.exit(); }}}Copy the code

AsyncInvoke An asynchronous call, similar to a synchronous call, that leverages AsyncEntry.

// SentinelDubboConsumerFilter.java private Result asyncInvoke(Invoker<? > invoker, Invocation invocation) { LinkedList<EntryHolder> queue = new LinkedList<>(); String prefix = DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey(); String interfaceResourceName = getInterfaceName(invoker, prefix); String methodResourceName = getMethodName(invoker, invocation, prefix); try { queue.push(new EntryHolder( SphU.asyncEntry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT), null)); queue.push(new EntryHolder( SphU.asyncEntry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT, 1, invocation.getArguments()), invocation.getArguments())); Result result = invoker.invoke(invocation); Result. WhenCompleteWithContext ((r, throwable) - > {/ / handle the abnormal Block throwable error = throwable; if (error == null) { error = Optional.ofNullable(r).map(Result::getException).orElse(null); } while (! queue.isEmpty()) { EntryHolder holder = queue.pop(); Tracer.traceEntry(error, holder.entry); / / exit exitEntry (holder); }}); return result; } catch (BlockException e) { while (! queue.isEmpty()) { exitEntry(queue.pop()); } return DubboAdapterGlobalConfig.getConsumerFallback().handle(invoker, invocation, e); }}Copy the code

Sentinel ADAPTS to Dubbo and can register custom DubboFallback methods to handle blockExceptions. (Note that non-blockExceptions do not enter fallback)

@FunctionalInterface public interface DubboFallback { Result handle(Invoker<? > invoker, Invocation invocation, BlockException ex); } // demo public static void registryCustomFallbackForCustomException() { DubboAdapterGlobalConfig.setConsumerFallback( (invoker, invocation, ex) -> AsyncRpcResult.newDefaultAsyncResult(new RuntimeException("fallback"), invocation)); }Copy the code

By default, converting BlockException to RuntimeException is returned.

public class DefaultDubboFallback implements DubboFallback {

    @Override
    public Result handle(Invoker<?> invoker, Invocation invocation, BlockException ex) {
        return AsyncRpcResult.newDefaultAsyncResult(ex.toRuntimeException(), invocation);
    }
}
Copy the code

3, the Provider

SentinelDubboProviderFilter responsible for performing Sentinel core API

@Activate(group = PROVIDER) public class SentinelDubboProviderFilter extends BaseSentinelDubboFilter { public SentinelDubboProviderFilter() { RecordLog.info("Sentinel Apache Dubbo provider filter initialized"); } @Override String getMethodName(Invoker invoker, Invocation invocation, String prefix) { return DubboUtils.getMethodResourceName(invoker, invocation, prefix); } @Override String getInterfaceName(Invoker invoker, String prefix) { return DubboUtils.getInterfaceName(invoker, prefix); } @Override public Result invoke(Invoker<? > invoker, Throws RpcException {// Parse the source system String Origin = DubboAdapterGlobalConfig.getOriginParser().parse(invoker, invocation); if (null == origin) { origin = ""; } Entry interfaceEntry = null; Entry methodEntry = null; String prefix = DubboAdapterGlobalConfig.getDubboProviderResNamePrefixKey(); String interfaceResourceName = getInterfaceName(Invoker, prefix); String methodResourceName = getMethodName(invoker, invocation, prefix); Try {// contextutil. enter(methodResourceName, origin) according to the methodResourceName; // entry interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN); methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN, invocation.getArguments());  // Invocation Result Result = Invocation. Invoke (Invocation); If (result.hasException()) {tracer.traceEntry (result.getException(), interfaceEntry); Tracer.traceEntry(result.getException(), methodEntry); } return result; } catch (BlockException e) { // fallback return DubboAdapterGlobalConfig.getProviderFallback().handle(invoker, invocation, e); } catch (RpcException e) {// Record RPC exception tracer. traceEntry(e, interfaceEntry); Tracer.traceEntry(e, methodEntry); throw e; } finally {// Exit if (methodEntry! = null) { methodEntry.exit(1, invocation.getArguments()); } if (interfaceEntry ! = null) { interfaceEntry.exit(); } ContextUtil.exit(); }}}Copy the code

By default, the Provider gets the source system from key=dubboApplication in the Attachment.

public class DefaultDubboOriginParser implements DubboOriginParser { @Override public String parse(Invoker<? > invoker, Invocation invocation) { return DubboUtils.getApplication(invocation, ""); } } // DubboUtils.java public static String getApplication(Invocation invocation, String defaultValue) { if (invocation == null || invocation.getAttachments() == null) { throw new IllegalArgumentException("Bad invocation instance"); } return invocation.getAttachment(SENTINEL_DUBBO_APPLICATION_KEY, defaultValue); }Copy the code

Four, SpringCloud

The spring-Cloud-starter-Alibaba-Sentinel module provides sentinel support for SpringCloud. This section is based on version 2.2.5.release.

  1. Sentinel supporting RestTemplate
  2. Sentinel supporting Feign
  3. SpringMVC automatically configures Sentinel interceptors

1. RestTemplate is supported

To register a RestTemplate in Java Config, add the SentinelRestTemplate annotation to the method to access Sentinel.

@Configuration public static class TestConfig1 { @Bean @SentinelRestTemplate(fallback = "fbk") RestTemplate restTemplate() { return new RestTemplate(); }}Copy the code

The reason is injected SentinelBeanPostProcessor SentinelAutoConfiguration automatic configuration class.

// SentinelAutoConfiguration.java
@Bean
@ConditionalOnMissingBean
@ConditionalOnClass(name = "org.springframework.web.client.RestTemplate")
@ConditionalOnProperty(name = "resttemplate.sentinel.enabled", havingValue = "true",
                       matchIfMissing = true)
public SentinelBeanPostProcessor sentinelBeanPostProcessor(
  ApplicationContext applicationContext) {
  return new SentinelBeanPostProcessor(applicationContext);
}
Copy the code

SentinelBeanPostProcessor in Bean after initialization, for RestTemplate joined SentinelProtectInterceptor interceptors.

// SentinelBeanPostProcessor.java @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { // ... RestTemplate restTemplate = (RestTemplate) bean; String interceptorBeanName = interceptorBeanNamePrefix + "@" + bean.toString(); registerBean(interceptorBeanName, sentinelRestTemplate, (RestTemplate) bean); SentinelProtectInterceptor sentinelProtectInterceptor = applicationContext .getBean(interceptorBeanName, SentinelProtectInterceptor.class); restTemplate.getInterceptors().add(0, sentinelProtectInterceptor); } / / registered SentinelProtectInterceptor interceptors to the Spring container private void registerBean (String interceptorBeanName, SentinelRestTemplate sentinelRestTemplate, RestTemplate restTemplate) { DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext .getAutowireCapableBeanFactory(); BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder .genericBeanDefinition(SentinelProtectInterceptor.class); beanDefinitionBuilder.addConstructorArgValue(sentinelRestTemplate); beanDefinitionBuilder.addConstructorArgValue(restTemplate); BeanDefinition interceptorBeanDefinition = beanDefinitionBuilder .getRawBeanDefinition(); beanFactory.registerBeanDefinition(interceptorBeanName, interceptorBeanDefinition); }Copy the code

SentinelProtectInterceptor intercept method, intercepts all requests, perform Sentinel API.

public class SentinelProtectInterceptor implements ClientHttpRequestInterceptor { private final SentinelRestTemplate sentinelRestTemplate; private final RestTemplate restTemplate; public SentinelProtectInterceptor(SentinelRestTemplate sentinelRestTemplate, RestTemplate restTemplate) { this.sentinelRestTemplate = sentinelRestTemplate; this.restTemplate = restTemplate; } @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { URI uri = request.getURI(); HostResource = request.getMethod().toString() + ":" + uri.getScheme() + "://" + uri.gethost () + (uri.getPort() == -1 ? "" : ":" + uri.getPort()); // host+path resource String hostWithPathResource = hostResource + uri.getPath(); boolean entryWithPath = true; if (hostResource.equals(hostWithPathResource)) { entryWithPath = false; } / / URL processing Method urlCleanerMethod = BlockClassRegistry. LookupUrlCleaner (sentinelRestTemplate. UrlCleanerClass (), sentinelRestTemplate.urlCleaner()); if (urlCleanerMethod ! = null) { hostWithPathResource = (String) methodInvoke(urlCleanerMethod, hostWithPathResource); } Entry hostEntry = null; Entry hostWithPathEntry = null; ClientHttpResponse response = null; try { // entry hostEntry = SphU.entry(hostResource, EntryType.OUT); if (entryWithPath) { hostWithPathEntry = SphU.entry(hostWithPathResource, EntryType.OUT); } // Business response = execution. Execute (request, body); / / abnormal records the if (this. RestTemplate. GetErrorHandler () hasError (response)) {Tracer. The trace (new IllegalStateException("RestTemplate ErrorHandler has error")); }} catch (Throwable e) {// Non-blockException if (! BlockException.isBlockException(e)) { Tracer.trace(e); } else {return handleBlockException(request, body, execution, (BlockException) e); }} finally {// Exit if (hostWithPathEntry! = null) { hostWithPathEntry.exit(); } if (hostEntry ! = null) { hostEntry.exit(); } } return response; }}Copy the code

Two resources are supported using RestTemplate:

  1. Host: request method: agreement: / / host: port, such as the GET: http://127.0.0.1:8080, to support the host dimension resource current limit;
  2. The host + path: request method: agreement: / / host: port/path, such as the GET: http://127.0.0.1:8080/hello, current limiting the resource support specific host + path;

For non BlockException SentinelProtectInterceptor merely record abnormal (Tracer. Trace);

For BlockException: If it is DegradeException, use fallback. Other BlockException blockHandler method, if can’t find the processing method, return SentinelClientHttpResponse (status = 200, Body =RestTemplate Request Block by sentinel).

// SentinelProtectInterceptor.java private ClientHttpResponse handleBlockException(HttpRequest request, byte[] body, ClientHttpRequestExecution execution, BlockException ex) { Object[] args = new Object[] { request, body, execution, ex }; // handle degrade if (isDegradeFailure(ex)) { Method fallbackMethod = extractFallbackMethod(sentinelRestTemplate.fallback(), sentinelRestTemplate.fallbackClass()); if (fallbackMethod ! = null) { return (ClientHttpResponse) methodInvoke(fallbackMethod, args); } else { return new SentinelClientHttpResponse(); } } // handle flow Method blockHandler = extractBlockHandlerMethod( sentinelRestTemplate.blockHandler(), sentinelRestTemplate.blockHandlerClass()); if (blockHandler ! = null) { return (ClientHttpResponse) methodInvoke(blockHandler, args); } else { return new SentinelClientHttpResponse(); }}Copy the code

2. Support Feign

Introducing spring – the cloud – starter – alibaba – sentinel, load automatically configure SentinelFeignAutoConfiguration, in the spring in the parent container injection prototype Feign. Builder, This prototype object is then created by the FeignContext child container and used to construct the FeignClient proxy object.

@Configuration(proxyBeanMethods = false) @ConditionalOnClass({ SphU.class, Feign.class }) public class SentinelFeignAutoConfiguration { @Bean @Scope("prototype") @ConditionalOnMissingBean @ConditionalOnProperty(name = "feign.sentinel.enabled") public Feign.Builder feignSentinelBuilder() { return SentinelFeign.builder(); }}Copy the code

Sentinelfeign.builder is the same as Hystrixfeign.Builder.

@Configuration(proxyBeanMethods = false) @ConditionalOnClass({HystrixCommand.class, HystrixFeign.class}) protected static class HystrixFeignConfiguration { protected HystrixFeignConfiguration() { } @Bean @Scope("prototype") @ConditionalOnMissingBean @ConditionalOnProperty( name = {"feign.hystrix.enabled"} ) public Builder feignHystrixBuilder() { return HystrixFeign.builder(); }}Copy the code

The SentinelFeign.Builder build method adds its own Contract agent and InvocationHandlerFactory.

// SentinelFeign.java public static final class Builder extends Feign.Builder implements ApplicationContextAware { private Contract contract = new Contract.Default(); @Override public Feign build() { super.invocationHandlerFactory(new InvocationHandlerFactory() { @Override public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) { // ... return new SentinelInvocationHandler(target, dispatch); }}); super.contract(new SentinelContractHolder(contract)); return super.build(); }Copy the code

This is also consistent with Hystrix’s approach.

// HystrixFeign.java
@Override
public Feign build() {
  return build(null);
}

Feign build(final FallbackFactory<?> nullableFallbackFactory) {
  super.invocationHandlerFactory(new InvocationHandlerFactory() {
    @Override
    public InvocationHandler create(Target target,
                                    Map<Method, MethodHandler> dispatch) {
      return new HystrixInvocationHandler(target, dispatch, setterFactory,
          nullableFallbackFactory);
    }
  });
  super.contract(new HystrixDelegatingContract(contract));
  return super.build();
}
Copy the code

SentinelInvocationHandler execution agent logic.

// SentinelInvocationHandler.java @Override public Object invoke(final Object proxy, final Method method, Final Object[] args) throws Throwable {// equals hasCode toString... Object result; MethodHandler methodHandler = this.dispatch.get(method); if (target instanceof Target.HardCodedTarget) { Target.HardCodedTarget hardCodedTarget = (Target.HardCodedTarget) target; / / in the way that derives from the Contract metadata MethodMetadata MethodMetadata = SentinelContractHolder. METADATA_MAP .get(hardCodedTarget.type().getName() + Feign.configKey(hardCodedTarget.type(), method)); if (methodMetadata == null) { result = methodHandler.invoke(args); } else {// resourceName = HTTPMethod + protocol + FeignClient annotation name or url attribute + request path String resourceName = methodMetadata.template().method().toUpperCase() + ":" + hardCodedTarget.url() + methodMetadata.template().path(); Entry entry = null; Try {// enter the context contextutil. enter(resourceName); // entry entry = SphU.entry(resourceName, EntryType.OUT, 1, args); // Execute the business result = methodHandler.invoke(args); } catch (Throwable ex) {BlockException if (! BlockException.isBlockException(ex)) { Tracer.trace(ex); } // Whether BlockException or not, fallback method if (fallbackFactory! = null) { try { Object fallbackResult = fallbackMethodMap.get(method) .invoke(fallbackFactory.create(ex), args); return fallbackResult; } catch (IllegalAccessException e) { throw new AssertionError(e); } catch (InvocationTargetException e) { throw new AssertionError(e.getCause()); } } else { throw ex; }} finally {// Exit if (entry! = null) { entry.exit(1, args); } ContextUtil.exit(); } } } else { result = methodHandler.invoke(args); } return result; }Copy the code

A few points to note here:

  1. Resource name: HTTPMethod + hardcodedTarget. url + request path, where HardcodedTarget. url= protocol +FeignClient name attribute or URL attribute. For example, the resource name of this method is: GET:http://sentinel-feign-provider-example/hello/{msg};

    @FeignClient(name = "sentinel-feign-provider-example", fallbackFactory = ClientFallbackFactory.class)
    public interface Client {
     // GET:http://sentinel-feign-provider-example/hello/{msg}
       @GetMapping("/hello/{msg}")
       String hello(@PathVariable("msg") String msg);
    }
    Copy the code
  2. Context: Use the resource name as the context;

  3. Fallback method execution timing: All exceptions, including blockexceptions, enter the defined fallback method;

Automatic configuration of SpringMVC

SentinelWebAutoConfiguration automatic configuration SentinelWebInterceptor interceptors, no configuration SentinelWebTotalInterceptor global interceptors.

// ...
public class SentinelWebAutoConfiguration implements WebMvcConfigurer {
   // ...

   @Override
   public void addInterceptors(InterceptorRegistry registry) {
      if (!sentinelWebInterceptorOptional.isPresent()) {
         return;
      }
      SentinelProperties.Filter filterConfig = properties.getFilter();
      registry.addInterceptor(sentinelWebInterceptorOptional.get())
            .order(filterConfig.getOrder())
            .addPathPatterns(filterConfig.getUrlPatterns());
   }

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

   @Bean
   @ConditionalOnProperty(name = "spring.cloud.sentinel.filter.enabled", matchIfMissing = true)
   public SentinelWebMvcConfig sentinelWebMvcConfig() {
      // ...
   }

}
Copy the code

conclusion

Sentinel ADAPTS to different frameworks, most of which use the Filter like extension points of the original framework to execute the Sentinel core API before and after method calls.

There are a few things to note about different frameworks:

  1. What was the timing of the interception?
  2. What are the things that are going to be resources and what are the names of the resources?
  3. How to handle exceptions?
  4. Source system? The context?

1, AspectJ

1-1. Method of intercepting SentinelResource annotations;

1-2. Each intercepted method becomes a resource, and the resource name takes precedence over the value attribute on the SentinelResource solution. If not, the current method signature is used as the resource name.

1-3. For BlockException, a blockHandler will be used for processing. If blockHandler is not found, a fallback will be used for processing. For non-blockExceptions, a Fallback is found. If no fallback is found, the original exception is thrown.

1-4. Setting the source system and context is not supported

2, for SpringMVC

2-1. Using SpringMVC’s HandlerInterceptor extension point, SentinelWebInterceptor intercepts all Controller methods that configure a path. Execute before and after the Controller method (DispatcherServlet#doService phase);

Specify HttpMethodSpecify=true and append HttpMethod to the path, e.g. GET:/hello;

2-3. The SentinelWebInterceptor’s preHandle throws a BlockException. You need to configure BlockExceptionHandler to implement BlockException processing. Official use DefaultBlockExceptionHandler provide default, returns the HTTP status code 429, response body Blocked by Sentinel (flow limiting). There is no fallback method for exceptions during business execution because users can use SpringMVC’s own global exception handling;

2-4. The default SpringMVC context is the same sentinel_spring_web_context. Implementing the RequestOriginParser interface allows you to customize the source system for retrieving requests.

3, Dubbo

3-1. Interception time: The Dubbo Filter is used to intercept before the client calls the server interface and intercept before the server receives the request and executes the business.

3-2. Dubbo’s service interfaces and methods (including the calling side and the server side) will automatically become resources in Sentinel; Interface resources fully qualified name called interface, such as com. Alibaba. CSP. The sentinel. Demo. Apache. Dubbo. FooService; Method called interface resources fully qualified name: method signature, such as com. Alibaba. CSP. The sentinel. Demo. Apache. Dubbo. FooService: sayHello (Java. Lang. String);

3-3. For BlockException, use the DubboFallback implementation class. By default, DefaultDubboFallback converts BlockException to RuntimeException. Non-blockexceptions are not processed;

3-4. For the Consumer terminal, there is no context or source system because it is the outlet flow. For the Provider end, the context is the method resource name, and the source system is the application application name passed in by the Consumer end through the Attachment.

4, RestTemplate

4-1, intercept time: HTTP request before, through SentinelProtectInterceptor interceptors to intercept;

4-2, resource name: HttpMethod + host, the host HttpMethod + + path, such as the GET: http://127.0.0.1:8080, a GET: http://127.0.0.1:8080/hello;

4-3. Processing BlockException: If it is DegradeException, fallback is used. For other blockExceptions, use the blockHandler method. If you can’t find treatment, return SentinelClientHttpResponse (status = 200, body = RestTemplate request block by sentinel). Non-blockexceptions are not handled;

4-4. Because it is an outlet flow, there is no context and no source system;

5, Feign

5-1, intercept time: perform the actual Feign method calls, SentinelInvocationHandler acting intercept method;

5-2, resource name: HTTPMethod + protocol + FeignClient annotation name or URL attribute + request path, such as GET:http://sentinel-feign-provider-example/hello/{msg};

5-3. For all exceptions, including BlockException, if fallback exists, execute fallback; otherwise, throw fallback directly.

5-4. Although it is an outlet traffic, it still uses the resource name as the context name, but there is no source system;