一、概述
在SpringBoot启动过程中,会加载外部依赖中META-INF/spring.factories 声明的类。其中最重要的两个类分别为SentinelWebAutoConfiguration 和SentinelAutoConfiguration 。下面针对这两个类进行分析,逐渐深入理解流量是如何被Sentinel拦截的。
SentinelWebAutoConfiguration 针对web项目,即springmvc扩展包,默认把所有的url的入口添加为sentinel资源SentinelAutoConfiguration 针对用户添加@SentinelResource注解的地方添加为sentinel资源
上述2个入口最终都是利用代理方式实现的,并且共用底层的代理类
二、源码
2.1 SentinelAutoConfiguration
META-INF/spring.factories 中包含SentinelAutoConfiguration,会在启动启动的时候进行加载,并且会加载@Bean的方法去实例化对象:
package com.alibaba.cloud.sentinel.custom;
@Configuration
public class SentinelAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
}
SentinelResourceAspect负责创建代理,实际执行的时候,也确实会调用到该类:
SentinelResourceAspect源码:
package com.alibaba.csp.sentinel.annotation.aspectj;
@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);
String resourceName = getResourceName(annotation.value(), originMethod);
EntryType entryType = annotation.entryType();
Entry entry = null;
try {
entry = SphU.entry(resourceName, entryType, 1, pjp.getArgs());
Object result = pjp.proceed();
return result;
} catch (BlockException ex) {
return handleBlockException(pjp, annotation, ex);
} catch (Throwable ex) {
traceException(ex, annotation);
throw ex;
} finally {
if (entry != null) {
entry.exit(1, pjp.getArgs());
}
}
}
}
SentinelResourceAspect是Sentinel中的核心切面,在SentinelAutoConfiguration被加载时,注入了一个SentinelResourceAspect的Bean对象,aspect的@around拦截标注有@SentinelResource的注解。
- Step1获取@SentinelResource注解;
- Step2获取@SentinelResource中的资源名;
- Step3执行Sentinel核心功能。
通过上面SentinelWebAutoConfiguration和SentinelAutoConfiguration两个类的加载,成功将流量引入Sentinel的核心入口SphU.entry ,下面我们对Sentinel的归一入口进行讲解。
为什么说是统一入口,即不管你是硬编码还是通过@SentinelResource注解 又或是通过集成alibaba+springmvc,sentinel最终的入口都是SphU.entry
2.1.1 SphU.entry
SphU.entry是Sentinel的归一入口,所有流量在熔断限流前都由此进入,此功能实现了对资源进行规则检查。在SphU.entry中的核心功能是entryWithPriority。
com.alibaba.csp.sentinel.CtSph:
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
throws BlockException {
Context context = ContextUtil.getContext();
ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
Entry e = new CtEntry(resourceWrapper, chain, context);
try {
chain.entry(context, resourceWrapper, null, count, prioritized, args);
} catch (BlockException e1) {
e.exit(count, args);
throw e1;
} catch (Throwable e1) {
RecordLog.info("Sentinel unexpected exception", e1);
}
return e;
}
entryWithPriority() 是Sentinel的核心功能:
- Step1创建上下文Context、Slot Chain;
- Step2结合Context和Slot Chain;
- Step3进入Slot Chain进行规则检查并返回结果。
2.2 SentinelWebAutoConfiguration
存在新版本合和旧版本之分
SentinelWebAutoConfiguration 针对web项目,即springmvc扩展包,默认把所有的url的入口添加为sentinel资源,这样不用每个url的入口手动添加@SentinelResource标签了
2.2.1 新版本
可以参见 【Sentinel入门】05 springmvc 集成Sentinel & springboot集成Sentinel & 链路模式失效 & WebContextUnify & CommonFilter,里面包含新版本的用法,基于springboot,需要手动引入SentinelWebInterceptor代理,本篇是alibaba源码,通过spi机制,自动实现引入SentinelWebInterceptor代理
至少从 spring cloud alibaba 2.2.1.RELEASE 版本开始,已经采用新版本
特点 是 引入 sentinel-spring-webmvc-dapter 包, 基于 Spring 的 Interceptor 代理拦截资源:
...
@Configuration
public class SentinelWebAutoConfiguration implements WebMvcConfigurer {
@Autowired
private SentinelProperties properties;
@Autowired
private Optional<UrlCleaner> urlCleanerOptional;
@Autowired
private Optional<BlockExceptionHandler> blockExceptionHandlerOptional;
@Autowired
private Optional<RequestOriginParser> requestOriginParserOptional;
@Autowired
private Optional<SentinelWebInterceptor> sentinelWebInterceptorOptional;
@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() {
SentinelWebMvcConfig sentinelWebMvcConfig = new SentinelWebMvcConfig();
sentinelWebMvcConfig.setHttpMethodSpecify(properties.getHttpMethodSpecify());
sentinelWebMvcConfig.setWebContextUnify(properties.getWebContextUnify());
if (blockExceptionHandlerOptional.isPresent()) {
blockExceptionHandlerOptional
.ifPresent(sentinelWebMvcConfig::setBlockExceptionHandler);
}
else {
if (StringUtils.hasText(properties.getBlockPage())) {
sentinelWebMvcConfig.setBlockExceptionHandler(((request, response,
e) -> response.sendRedirect(properties.getBlockPage())));
}
else {
sentinelWebMvcConfig
.setBlockExceptionHandler(new DefaultBlockExceptionHandler());
}
}
urlCleanerOptional.ifPresent(sentinelWebMvcConfig::setUrlCleaner);
requestOriginParserOptional.ifPresent(sentinelWebMvcConfig::setOriginParser);
return sentinelWebMvcConfig;
}
2.2.1 (旧版本)
META-INF/spring.factories 中包含SentinelWebAutoConfiguration ,会在启动启动的时候进行加载,并且会加载@Bean的方法去实例化对象:
package com.alibaba.cloud.sentinel;
@Configuration
public class SentinelWebAutoConfiguration {
@Bean
@ConditionalOnProperty(name = "spring.cloud.sentinel.filter.enabled", matchIfMissing = true)
public FilterRegistrationBean sentinelFilter() {
FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();
SentinelProperties.Filter filterConfig = properties.getFilter();
if (filterConfig.getUrlPatterns() == null
|| filterConfig.getUrlPatterns().isEmpty()) {
List<String> defaultPatterns = new ArrayList<>();
defaultPatterns.add("/*");
filterConfig.setUrlPatterns(defaultPatterns);
}
registration.addUrlPatterns(filterConfig.getUrlPatterns().toArray(new String[0]));
Filter filter = new CommonFilter();
registration.setFilter(filter);
registration.setOrder(filterConfig.getOrder());
registration.addInitParameter("HTTP_METHOD_SPECIFY",
String.valueOf(properties.getHttpMethodSpecify()));
return registration;
}
}
在SentinelWebAutoConfiguration 被加载时,注入了一个FilterRegistrationBean 的Bean对象:
- Step1创建一个注册Filter的FilterRegistrationBean对象registration;
- Step2创建了一个CommonFilter,这个后面重点研究;
- Step3将CommonFilter放入到filterChain中。
旧版本中,是利用Filter原理,进行过滤:
package com.alibaba.csp.sentinel.adapter.servlet;
public class CommonFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
String target = FilterUtil.filterTarget(sRequest);
ContextUtil.enter(target, origin);
entry = SphU.entry(target, EntryType.IN);
chain.doFilter(request, response);
}
ContextUtil.exit();
}
}
}
CommonFilter,这是个内置的Filter 实现类,专门用于sentinel的功能:
package com.alibaba.csp.sentinel.adapter.servlet;
public class CommonFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
String target = FilterUtil.filterTarget(sRequest);
ContextUtil.enter(target, origin);
entry = SphU.entry(target, EntryType.IN);
chain.doFilter(request, response);
}
ContextUtil.exit();
}
}
}
CommonFilter会拦截所有进入Tomcat的请求:
- Step1截取请求中的URL作为“资源名”;
- Step2创建一个name为“资源名”的Context;
- Step3执行Sentinel的核心功能SphU.entry,这个后面重点研究。
参考
Sentinel源码(流量入口)
|