首先,我们回顾一下在接入spring-cloud-alibaba-sentinel时,我们使用了@SentinelResouece标记了需要Sentinel进行管控的资源,并且在控制台可以发现我们的上报的资源。
那么到底是如何进行服务注册的呢?
启动的时候,Sentinel在组件中的工作
Sentinel自动配置策略依赖于spring.cloud.sentinel.enabled。
启动时,spring-boot的自动配置类SPI如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.alibaba.sentinel.SentinelWebAutoConfiguration,\
org.springframework.cloud.alibaba.sentinel.endpoint.SentinelEndpointAutoConfiguration,\
org.springframework.cloud.alibaba.sentinel.custom.SentinelAutoConfiguration,\
org.springframework.cloud.alibaba.sentinel.feign.SentinelFeignAutoConfiguration
因为目前暂时没有涉及到feign,我们可以将这部分略过,剖析其余三个bean的作用。
SentinelWebAutoConfiguration
用于对filter进行通配符配置和排序。
@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());
log.info("[Sentinel Starter] register Sentinel with urlPatterns: {}.",
filterConfig.getUrlPatterns());
return registration;
}
SentinelEndpointAutoConfiguration
众所周知,Spring Boot中的actuate是负责汇报应用内信息的,而SentinelEndpointAutoConfiguration就是为了注册Sentinel的汇报信息的。
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public SentinelEndpoint sentinelEndPoint(SentinelProperties sentinelProperties) {
return new SentinelEndpoint(sentinelProperties);
}
我们继续看SentinelEndpoint:
@Endpoint(id = "sentinel")
public class SentinelEndpoint {
private final SentinelProperties sentinelProperties;
public SentinelEndpoint(SentinelProperties sentinelProperties) {
this.sentinelProperties = sentinelProperties;
}
@ReadOperation
public Map<String, Object> invoke() {
final Map<String, Object> result = new HashMap<>();
if (sentinelProperties.isEnabled()) {
result.put("appName", AppNameUtil.getAppName());
result.put("logDir", LogBase.getLogBaseDir());
result.put("logUsePid", LogBase.isLogNameUsePid());
result.put("blockPage", WebServletConfig.getBlockPage());
result.put("metricsFileSize", SentinelConfig.singleMetricFileSize());
result.put("metricsFileCharset", SentinelConfig.charset());
result.put("totalMetricsFileCount", SentinelConfig.totalMetricFileCount());
result.put("consoleServer", TransportConfig.getConsoleServer());
result.put("clientIp", TransportConfig.getHeartbeatClientIp());
result.put("heartbeatIntervalMs", TransportConfig.getHeartbeatIntervalMs());
result.put("clientPort", TransportConfig.getPort());
result.put("coldFactor", sentinelProperties.getFlow().getColdFactor());
result.put("filter", sentinelProperties.getFilter());
result.put("datasource", sentinelProperties.getDatasource());
final Map<String, Object> rules = new HashMap<>();
result.put("rules", rules);
rules.put("flowRules", FlowRuleManager.getRules());
rules.put("degradeRules", DegradeRuleManager.getRules());
rules.put("systemRules", SystemRuleManager.getRules());
rules.put("authorityRule", AuthorityRuleManager.getRules());
rules.put("paramFlowRule", ParamFlowRuleManager.getRules());
}
return result;
}
}
里面包括了所有的注册信息,包括当前的服务信息和Sentinel网关功能信息。
SentinelAutoConfiguration
SentinelAutoConfiguration会进行设置关键信息的系统变量:
@PostConstruct
private void init() {
// ① Sentinel系统变量设置
if (StringUtils.isEmpty(System.getProperty(LogBase.LOG_DIR))
&& StringUtils.hasText(properties.getLog().getDir())) {
System.setProperty(LogBase.LOG_DIR, properties.getLog().getDir());
}
if (StringUtils.isEmpty(System.getProperty(LogBase.LOG_NAME_USE_PID))
&& properties.getLog().isSwitchPid()) {
System.setProperty(LogBase.LOG_NAME_USE_PID,
String.valueOf(properties.getLog().isSwitchPid()));
}
...
// ② 默认URL处理方式配置
urlBlockHandlerOptional.ifPresent(WebCallbackManager::setUrlBlockHandler);
urlCleanerOptional.ifPresent(WebCallbackManager::setUrlCleaner);
requestOriginParserOptional.ifPresent(WebCallbackManager::setRequestOriginParser);
// ③ 是否进行加载
if (properties.isEager()) {
InitExecutor.doInit();
}
}
- Sentinel在服务期间使用到的全局变量,设置到系统变量中。
- 默认的URL处理。
- 如果关闭懒加载,这个时候会进行Sentinel全局配置的加载。懒加载的开关是spring.cloud.sentinel.eager,默认为true。
上述动作均发生在Bean的初始化过程中。
数据源解析
负责数据源解析的类是SentinelDataSourceHandler,它在初始化单例Bean的回调中进行对数据源的解析。
public void afterSingletonsInstantiated() {
// ① 遍历配置信息
sentinelProperties.getDatasource()
.forEach((dataSourceName, dataSourceProperties) -> {
try {
List<String> validFields = dataSourceProperties.getValidField();
// ② 规则校验
if (validFields.size() != 1) {
...
}
AbstractDataSourceProperties abstractDataSourceProperties = dataSourceProperties
.getValidDataSourceProperties();
// ③ 前置检查
abstractDataSourceProperties.preCheck(dataSourceName);
// ④ 注册为Bean
registerBean(abstractDataSourceProperties, dataSourceName
+ "-sentinel-" + validFields.get(0) + "-datasource");
}
...
});
}
我们简单介绍一下SentinelProperties,它是一个存在于每个服务的全局配置属性文件。
我们在应用配置文件(application.yml)中配置的,前缀是spring.cloud.sentinel的所有属性,都会解析到这个类对象中。
而在此类的加持下,我们配置的所有数据源(datasource)信息,会被解析到Map<String, DataSourcePropertiesConfiguration>对象中,String就是我们配置数据源时采用的类型,DataSourcePropertiesConfiguration就是我们相应的配置。
截稿前为止,Sentinel目前仅支持一种配置规则方式。
对上面的源码进行以下解读:
- 遍历SentinelProperties中的数据源配置信息。
- 规则方式数量限制,目前仅支持一种配置规则方式。
- 属性配置的前置检查。比如文件配置方式会设置文件地址的绝对路径,Nacos配置方式会检查Nacos的服务端地址和一些需要与Nacos进行交互的节点信息的完备性。
- 将数据源属性注册为Bean。
我们接下来看SentinelDataSourceHandler#registerBean()方法:
private void registerBean(final AbstractDataSourceProperties dataSourceProperties,
String dataSourceName) {
// ① 将数据源配置中的私有属性字段设置为可使用,并放入到属性map中
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());
// ③ 准备构建BeanDefinition
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) {
...
}
}
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());
}
// ⑤ 添加默认转换器
builder.addPropertyReference("converter",
"sentinel-" + propertyValue.toString() + "-"
+ dataSourceProperties.getRuleType().getName()
+ "-converter");
}
}
else if (CONVERTER_CLASS_FIELD.equals(propertyName)) {
return;
}
else {
// ⑥ 直接注册转换器
Optional.ofNullable(propertyValue)
.ifPresent(v -> builder.addPropertyValue(propertyName, v));
}
});
// ⑦ 通过Bean工厂注册Bean
this.beanFactory.registerBeanDefinition(dataSourceName,
builder.getBeanDefinition());
// ⑧ 实例化Bean
AbstractDataSource newDataSource = (AbstractDataSource) this.beanFactory
.getBean(dataSourceName);
// ⑨ 记录并校验数据源中的规则类型
logAndCheckRuleType(newDataSource, dataSourceName,
dataSourceProperties.getRuleType().getClazz());
// ⑩ 将规则注册到RuleManager中
dataSourceProperties.postRegister(newDataSource);
}
- 将数据源配置中的私有属性字段设置为可访问,并将键值对放入到属性map中。
- 继续添加一些注册Bean时需要的额外信息。
- 准备构建BeanDefinition。
- 过滤属性信息,这时候做的是找到对应的转换器。
- 获取配置的文件类型。如果是自定义的,需要使用者提供自定义的转换器Bean。Sentinel也提供了两种默认的转换器策略:JSON和XML。
- 如果不需要上面的处理,直接将属性放入到BeanDefinition中,这里的属性值得是除了dataType和converterClass的其他属性。
- 使用BeanDefintion注册Bean。
- 实例化Bean。
- 记录并校验数据源中的规则类型。
- 将规则注册到RuleManager中。
我们继续来看下SentinelDataSourceHandler#logAndCheckRuleType()方法,这里面有一些我们配置规则时,需要注意到的地方:
private void logAndCheckRuleType(AbstractDataSource dataSource, String dataSourceName,
Class<? extends AbstractRule> ruleClass) {
Object ruleConfig;
try {
// ① 加载配置
ruleConfig = dataSource.loadConfig();
}
catch (Exception e) {
log.error("[Sentinel Starter] DataSource " + dataSourceName
+ " loadConfig error: " + e.getMessage(), e);
return;
}
if (ruleConfig instanceof List) {
List convertedRuleList = (List) ruleConfig;
if (CollectionUtils.isEmpty(convertedRuleList)) {
log.warn("[Sentinel Starter] DataSource {} rule list is empty.",
dataSourceName);
return;
}
// ② 规则类型校验
if (convertedRuleList.stream()
.noneMatch(rule -> rule.getClass() == ruleClass)) {
...
}
else if (!convertedRuleList.stream()
.allMatch(rule -> rule.getClass() == ruleClass)) {
log.warn("[Sentinel Starter] DataSource {} all rules are not {} type.",
dataSourceName, ruleClass.getSimpleName());
}
else {
log.info("[Sentinel Starter] DataSource {} load {} {}", dataSourceName,
convertedRuleList.size(), ruleClass.getSimpleName());
}
}
// ③ 非List的规则集合不予解析
else {
...
}
}
- 加载配置,并且使用指定的解析器进行解析。比如文件配置方式,就会从文件中读取配置信息;Nacos方式就会从Nacos端拉取配置信息。
- 规则类型校验。集合中类型必须满足有且仅有指定规则类型。
规则集合必须是List。
接下来我们看AbstractDataSourceProperties#postRegister()方法:
public void postRegister(AbstractDataSource dataSource) {
switch (this.getRuleType()) {
case FLOW:
FlowRuleManager.register2Property(dataSource.getProperty());
break;
case DEGRADE:
DegradeRuleManager.register2Property(dataSource.getProperty());
break;
case PARAM_FLOW:
ParamFlowRuleManager.register2Property(dataSource.getProperty());
break;
case SYSTEM:
SystemRuleManager.register2Property(dataSource.getProperty());
break;
case AUTHORITY:
AuthorityRuleManager.register2Property(dataSource.getProperty());
break;
default:
break;
}
}
它会根据规则类型,将属性注册到对应的RuleManager中。
每种类型的RuleManager#register2Property()代码类似:
AssertUtil.notNull(property, "property cannot be null");
synchronized (LISTENER) {
RecordLog.info("[DegradeRuleManager] Registering new property to degrade rule manager");
currentProperty.removeListener(LISTENER);
property.addListener(LISTENER);
currentProperty = property;
}
使用互斥锁的方式,生成用于监听配置变化的监听器。
看起来索然无味,但是却内涵深机。
在添加监听器时,我们会进行如下操作,DynamicSentinelProperty#addListener():
@Override
public void addListener(PropertyListener<T> listener) {
listeners.add(listener);
listener.configLoad(value);
}
- 向监听器列表中添加对应的监听器。
- 使用监听器触发一次属性加载。
也就是说,我们的所有动态规则配置,会在此时此刻,通过监听器初始化的方式,进行一次规则加载。
我们完成了对Sentinel服务注册和规则的解析。