本文主要分析spring-cloud-starter-alibaba-sentinel中的源碼,看看springcloud alibaba環境下內建sentinel做了些什麼,實際上已經超出了Sentinel自身部分了。
首先既然是springcloud,那麼配置入口先找spring.factories檔案中的配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.cloud.sentinel.SentinelWebAutoConfiguration,\
com.alibaba.cloud.sentinel.SentinelWebFluxAutoConfiguration,\
com.alibaba.cloud.sentinel.endpoint.SentinelEndpointAutoConfiguration,\
com.alibaba.cloud.sentinel.custom.SentinelAutoConfiguration,\
com.alibaba.cloud.sentinel.feign.SentinelFeignAutoConfiguration
org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker=\
com.alibaba.cloud.sentinel.custom.SentinelCircuitBreakerConfiguration
這裡我們看幾個配置類
SentinelWebAutoConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnProperty(name = "spring.cloud.sentinel.enabled", matchIfMissing = true)
@ConditionalOnClass(SentinelWebInterceptor.class)
@EnableConfigurationProperties(SentinelProperties.class)
public class SentinelWebAutoConfiguration implements WebMvcConfigurer {
private static final Logger log = LoggerFactory
.getLogger(SentinelWebAutoConfiguration.class);
@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());
log.info(
"[Sentinel Starter] register SentinelWebInterceptor with urlPatterns: {}.",
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());
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;
}
}
實作了WebMvcConfigurer接口。addInterceptors方法根據spring.cloud.sentinel.filter配置添加攔截器。
攔截器是SentinelWebInterceptor,是Sentinel-Adapter裡的了。看一下它繼承的abstract類AbstractSentinelInterceptor:
public abstract class AbstractSentinelInterceptor implements HandlerInterceptor
實作攔截器接口,preHandle方法:
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
try {
String resourceName = getResourceName(request);
if (StringUtil.isNotEmpty(resourceName)) {
// Parse the request origin using registered origin parser.
String origin = parseOrigin(request);
ContextUtil.enter(SENTINEL_SPRING_WEB_CONTEXT_NAME, origin);
Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN);
setEntryInRequest(request, baseWebMvcConfig.getRequestAttributeName(), entry);
}
return true;
} catch (BlockException e) {
handleBlockException(request, response, e);
return false;
}
}
可以看到,在filter指定的攔截範圍内,執行entry方法。
SentinelAutoConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.sentinel.enabled", matchIfMissing = true)
@EnableConfigurationProperties(SentinelProperties.class)
public class SentinelAutoConfiguration {
@Value("${project.name:${spring.application.name:}}")
private String projectName;
@Autowired
private SentinelProperties properties;
@PostConstruct
private void init() {
// 系統配置
... 省略部分代碼
// earlier initialize
// 初始化操作
// InitFunc SPI的實作
if (properties.isEager()) {
InitExecutor.doInit();
}
}
@Bean
@ConditionalOnMissingBean
public SentinelResourceAspect sentinelResourceAspect() {
// 基于@SentinelResource注解的Aspect,有@SentinelResource注解的則認為是資源,會進行entry攔截。
return new SentinelResourceAspect();
}
@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);
}
@Bean
@ConditionalOnMissingBean
public SentinelDataSourceHandler sentinelDataSourceHandler(
DefaultListableBeanFactory beanFactory, SentinelProperties sentinelProperties,
Environment env) {
return new SentinelDataSourceHandler(beanFactory, sentinelProperties, env);
}
// 轉換器配置
@ConditionalOnClass(ObjectMapper.class)
@Configuration(proxyBeanMethods = false)
protected static class SentinelConverterConfiguration {
@Configuration(proxyBeanMethods = false)
protected static class SentinelJsonConfiguration {
private ObjectMapper objectMapper = new ObjectMapper();
public SentinelJsonConfiguration() {
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
false);
}
@Bean("sentinel-json-flow-converter")
public JsonConverter jsonFlowConverter() {
return new JsonConverter(objectMapper, FlowRule.class);
}
@Bean("sentinel-json-degrade-converter")
public JsonConverter jsonDegradeConverter() {
return new JsonConverter(objectMapper, DegradeRule.class);
}
@Bean("sentinel-json-system-converter")
public JsonConverter jsonSystemConverter() {
return new JsonConverter(objectMapper, SystemRule.class);
}
@Bean("sentinel-json-authority-converter")
public JsonConverter jsonAuthorityConverter() {
return new JsonConverter(objectMapper, AuthorityRule.class);
}
@Bean("sentinel-json-param-flow-converter")
public JsonConverter jsonParamFlowConverter() {
return new JsonConverter(objectMapper, ParamFlowRule.class);
}
}
@ConditionalOnClass(XmlMapper.class)
@Configuration(proxyBeanMethods = false)
protected static class SentinelXmlConfiguration {
private XmlMapper xmlMapper = new XmlMapper();
public SentinelXmlConfiguration() {
xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
false);
}
@Bean("sentinel-xml-flow-converter")
public XmlConverter xmlFlowConverter() {
return new XmlConverter(xmlMapper, FlowRule.class);
}
@Bean("sentinel-xml-degrade-converter")
public XmlConverter xmlDegradeConverter() {
return new XmlConverter(xmlMapper, DegradeRule.class);
}
@Bean("sentinel-xml-system-converter")
public XmlConverter xmlSystemConverter() {
return new XmlConverter(xmlMapper, SystemRule.class);
}
@Bean("sentinel-xml-authority-converter")
public XmlConverter xmlAuthorityConverter() {
return new XmlConverter(xmlMapper, AuthorityRule.class);
}
@Bean("sentinel-xml-param-flow-converter")
public XmlConverter xmlParamFlowConverter() {
return new XmlConverter(xmlMapper, ParamFlowRule.class);
}
}
}
}
- @PostConstruct方法,初始化Sentinel環境,執行InitFunc的init
- SentinelResourceAspec
被@SentinelResource注解的添加了個Aspect,Around通知器。調用entry。@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) { // Should not go through here. throw new IllegalStateException("Wrong state for SentinelResource annotation"); } String resourceName = getResourceName(annotation.value(), originMethod); EntryType entryType = annotation.entryType(); int resourceType = annotation.resourceType(); Entry entry = null; 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; } finally { if (entry != null) { entry.exit(1, pjp.getArgs()); } } } }
-
SentinelBeanPostProcessor
實作了MergedBeanDefinitionPostProcessor接口
被@SentinelRestTemplate注解标注的,加到緩存裡@Override public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) { if (checkSentinelProtect(beanDefinition, beanType, beanName)) { SentinelRestTemplate sentinelRestTemplate; if (beanDefinition.getSource() instanceof StandardMethodMetadata) { sentinelRestTemplate = ((StandardMethodMetadata) beanDefinition .getSource()).getIntrospectedMethod() .getAnnotation(SentinelRestTemplate.class); } else { sentinelRestTemplate = beanDefinition.getResolvedFactoryMethod() .getAnnotation(SentinelRestTemplate.class); } // check class and method validation checkSentinelRestTemplate(sentinelRestTemplate, beanName); cache.put(beanName, sentinelRestTemplate); } }
RestTemplate添加了一個攔截器@Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (beanName != null && cache.containsKey(beanName)) { // add interceptor for each RestTemplate with @SentinelRestTemplate annotation StringBuilder interceptorBeanNamePrefix = new StringBuilder(); SentinelRestTemplate sentinelRestTemplate = cache.get(beanName); interceptorBeanNamePrefix .append(StringUtils.uncapitalize( SentinelProtectInterceptor.class.getSimpleName())) .append("_") .append(sentinelRestTemplate.blockHandlerClass().getSimpleName()) .append(sentinelRestTemplate.blockHandler()).append("_") .append(sentinelRestTemplate.fallbackClass().getSimpleName()) .append(sentinelRestTemplate.fallback()).append("_") .append(sentinelRestTemplate.urlCleanerClass().getSimpleName()) .append(sentinelRestTemplate.urlCleaner()); 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); } return bean; }
restTemplate.getInterceptors().add(0, sentinelProtectInterceptor);
看一下這個攔截器的方法:
com.alibaba.cloud.sentinel.custom.SentinelProtectInterceptor#intercept
@Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { URI uri = request.getURI(); String hostResource = request.getMethod().toString() + ":" + uri.getScheme() + "://" + uri.getHost() + (uri.getPort() == -1 ? "" : ":" + uri.getPort()); String hostWithPathResource = hostResource + uri.getPath(); boolean entryWithPath = true; if (hostResource.equals(hostWithPathResource)) { entryWithPath = false; } 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 { hostEntry = SphU.entry(hostResource, EntryType.OUT); if (entryWithPath) { hostWithPathEntry = SphU.entry(hostWithPathResource, EntryType.OUT); } response = execution.execute(request, body); if (this.restTemplate.getErrorHandler().hasError(response)) { Tracer.trace( new IllegalStateException("RestTemplate ErrorHandler has error")); } } catch (Throwable e) { if (!BlockException.isBlockException(e)) { Tracer.trace(e); } else { return handleBlockException(request, body, execution, (BlockException) e); } } finally { if (hostWithPathEntry != null) { hostWithPathEntry.exit(); } if (hostEntry != null) { hostEntry.exit(); } } return response; }
好的,已經看到entry方法了。
總結這個處理器SentinelBeanPostProcessor的作用:
對于使用RestTemplate進行資源請求的,如果在定義的RestTemplate對象上增加@SentinelRestTemplate注解,則會添加一個SentinelProtectInterceptor攔截器,對restTemplate的請求進行攔截,通過entry實作限流、降級等操作。比如:
此restTemplate的調用會entry@Bean @SentinelRestTemplate public RestTemplate restTemplate(){ return new RestTemplate(); }
-
SentinelDataSourceHandler
實作了SmartInitializingSingleton接口,看一下afterSingletonsInstantiated方法
就是注冊DataSource的Bean定義。前文提到的規則持久化部分。@Override public void afterSingletonsInstantiated() { sentinelProperties.getDatasource() .forEach((dataSourceName, dataSourceProperties) -> { try { List<String> validFields = dataSourceProperties.getValidField(); if (validFields.size() != 1) { log.error("[Sentinel Starter] DataSource " + dataSourceName + " multi datasource active and won't loaded: " + dataSourceProperties.getValidField()); return; } AbstractDataSourceProperties abstractDataSourceProperties = dataSourceProperties .getValidDataSourceProperties(); abstractDataSourceProperties.setEnv(env); abstractDataSourceProperties.preCheck(dataSourceName); registerBean(abstractDataSourceProperties, dataSourceName + "-sentinel-" + validFields.get(0) + "-datasource"); } catch (Exception e) { log.error("[Sentinel Starter] DataSource " + dataSourceName + " build error: " + e.getMessage(), e); } }); } private void registerBean(final AbstractDataSourceProperties dataSourceProperties, String dataSourceName) { Map<String, Object> propertyMap = Arrays .stream(dataSourceProperties.getClass().getDeclaredFields()) .collect(HashMap::new, (m, v) -> { try { v.setAccessible(true); m.put(v.getName(), v.get(dataSourceProperties)); } catch (IllegalAccessException e) { log.error("[Sentinel Starter] DataSource " + dataSourceName + " field: " + v.getName() + " invoke error"); throw new RuntimeException( "[Sentinel Starter] DataSource " + dataSourceName + " field: " + v.getName() + " invoke error", e); } }, HashMap::putAll); propertyMap.put(CONVERTER_CLASS_FIELD, dataSourceProperties.getConverterClass()); propertyMap.put(DATA_TYPE_FIELD, dataSourceProperties.getDataType()); BeanDefinitionBuilder builder = BeanDefinitionBuilder .genericBeanDefinition(dataSourceProperties.getFactoryBeanName()); propertyMap.forEach((propertyName, propertyValue) -> { Field field = ReflectionUtils.findField(dataSourceProperties.getClass(), propertyName); if (null == field) { return; } if (DATA_TYPE_FIELD.equals(propertyName)) { String dataType = StringUtils.trimAllWhitespace(propertyValue.toString()); if (CUSTOM_DATA_TYPE.equals(dataType)) { try { if (StringUtils .isEmpty(dataSourceProperties.getConverterClass())) { throw new RuntimeException("[Sentinel Starter] DataSource " + dataSourceName + "dataType is custom, please set converter-class " + "property"); } // construct custom Converter with 'converterClass' // configuration and register String customConvertBeanName = "sentinel-" + dataSourceProperties.getConverterClass(); if (!this.beanFactory.containsBean(customConvertBeanName)) { this.beanFactory.registerBeanDefinition(customConvertBeanName, BeanDefinitionBuilder .genericBeanDefinition( Class.forName(dataSourceProperties .getConverterClass())) .getBeanDefinition()); } builder.addPropertyReference("converter", customConvertBeanName); } catch (ClassNotFoundException e) { log.error("[Sentinel Starter] DataSource " + dataSourceName + " handle " + dataSourceProperties.getClass().getSimpleName() + " error, class name: " + dataSourceProperties.getConverterClass()); throw new RuntimeException("[Sentinel Starter] DataSource " + dataSourceName + " handle " + dataSourceProperties.getClass().getSimpleName() + " error, class name: " + dataSourceProperties.getConverterClass(), e); } } else { if (!dataTypeList.contains( StringUtils.trimAllWhitespace(propertyValue.toString()))) { throw new RuntimeException("[Sentinel Starter] DataSource " + dataSourceName + " dataType: " + propertyValue + " is not support now. please using these types: " + dataTypeList.toString()); } // converter type now support xml or json. // The bean name of these converters wrapped by // 'sentinel-{converterType}-{ruleType}-converter' builder.addPropertyReference("converter", "sentinel-" + propertyValue.toString() + "-" + dataSourceProperties.getRuleType().getName() + "-converter"); } } else if (CONVERTER_CLASS_FIELD.equals(propertyName)) { return; } else { // wired properties Optional.ofNullable(propertyValue) .ifPresent(v -> builder.addPropertyValue(propertyName, v)); } }); this.beanFactory.registerBeanDefinition(dataSourceName, builder.getBeanDefinition()); // init in Spring AbstractDataSource newDataSource = (AbstractDataSource) this.beanFactory .getBean(dataSourceName); // register property in RuleManager dataSourceProperties.postRegister(newDataSource); }
SentinelFeignAutoConfiguration
@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();
}
}
Sentinel與Feign的內建。
看一下builder
@Override
public Feign build() {
super.invocationHandlerFactory(new InvocationHandlerFactory() {
@Override
public InvocationHandler create(Target target,
Map<Method, MethodHandler> dispatch) {
// using reflect get fallback and fallbackFactory properties from
// FeignClientFactoryBean because FeignClientFactoryBean is a package
// level class, we can not use it in our package
Object feignClientFactoryBean = Builder.this.applicationContext
.getBean("&" + target.type().getName());
Class fallback = (Class) getFieldValue(feignClientFactoryBean,
"fallback");
Class fallbackFactory = (Class) getFieldValue(feignClientFactoryBean,
"fallbackFactory");
String beanName = (String) getFieldValue(feignClientFactoryBean,
"contextId");
if (!StringUtils.hasText(beanName)) {
beanName = (String) getFieldValue(feignClientFactoryBean, "name");
}
Object fallbackInstance;
FallbackFactory fallbackFactoryInstance;
// check fallback and fallbackFactory properties
if (void.class != fallback) {
fallbackInstance = getFromContext(beanName, "fallback", fallback,
target.type());
return new SentinelInvocationHandler(target, dispatch,
new FallbackFactory.Default(fallbackInstance));
}
if (void.class != fallbackFactory) {
fallbackFactoryInstance = (FallbackFactory) getFromContext(
beanName, "fallbackFactory", fallbackFactory,
FallbackFactory.class);
return new SentinelInvocationHandler(target, dispatch,
fallbackFactoryInstance);
}
return new SentinelInvocationHandler(target, dispatch);
}
private Object getFromContext(String name, String type,
Class fallbackType, Class targetType) {
Object fallbackInstance = feignContext.getInstance(name,
fallbackType);
if (fallbackInstance == null) {
throw new IllegalStateException(String.format(
"No %s instance of type %s found for feign client %s",
type, fallbackType, name));
}
if (!targetType.isAssignableFrom(fallbackType)) {
throw new IllegalStateException(String.format(
"Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s",
type, fallbackType, targetType, name));
}
return fallbackInstance;
}
});
super.contract(new SentinelContractHolder(contract));
return super.build();
}
建立一個InvocationHandler,這裡是SentinelInvocationHandler:
看一下它的invoke方法:
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable {
...省略部分代碼
Object result;
MethodHandler methodHandler = this.dispatch.get(method);
// only handle by HardCodedTarget
if (target instanceof Target.HardCodedTarget) {
Target.HardCodedTarget hardCodedTarget = (Target.HardCodedTarget) target;
MethodMetadata methodMetadata = SentinelContractHolder.METADATA_MAP
.get(hardCodedTarget.type().getName()
+ Feign.configKey(hardCodedTarget.type(), method));
// resource default is HttpMethod:protocol://url
if (methodMetadata == null) {
result = methodHandler.invoke(args);
}
else {
String resourceName = methodMetadata.template().method().toUpperCase()
+ ":" + hardCodedTarget.url() + methodMetadata.template().path();
Entry entry = null;
try {
ContextUtil.enter(resourceName);
entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
result = methodHandler.invoke(args);
}
catch (Throwable ex) {
// fallback handle
if (!BlockException.isBlockException(ex)) {
Tracer.trace(ex);
}
if (fallbackFactory != null) {
try {
Object fallbackResult = fallbackMethodMap.get(method)
.invoke(fallbackFactory.create(ex), args);
return fallbackResult;
}
catch (IllegalAccessException e) {
// shouldn't happen as method is public due to being an
// interface
throw new AssertionError(e);
}
catch (InvocationTargetException e) {
throw new AssertionError(e.getCause());
}
}
else {
// throw exception if fallbackFactory is null
throw ex;
}
}
finally {
if (entry != null) {
entry.exit(1, args);
}
ContextUtil.exit();
}
}
}
else {
// other target type using default strategy
result = methodHandler.invoke(args);
}
return result;
}
好了,看到entry調用了。 在執行feign調用前entry。
SentinelWebFluxAutoConfiguration
WebFlux的內建配置。