前言
在实际开发过程中,使用 SpringBoot 结合 Dubbo ,在项目启动的时候总是会出现一个, zookeeper net connected 的错误,最后发现是在 org.apache.dubbo.remoting.zookeeper.curator.CuratorZookeeperClient 构造方法中会去连接 zk,并且是同步阻塞形式的(代码如下)。时间设置的过短就会报错。
public CuratorZookeeperClient(URL url) {
super(url);
try {
// ......
client.getConnectionStateListenable().addListener(new CuratorConnectionStateListener(url));
client.start();
// 这里是一个同步阻塞等待,假如超过了 timeout 的时间,当前ZooKeeper客户端还是没有变成“已连接”状态,当前线程就会被唤醒,继续向下执行
boolean connected = client.blockUntilConnected(timeout, TimeUnit.MILLISECONDS);
// 判断当前客户端不是“已连接”状态,主动抛出异常
if (!connected) {
throw new IllegalStateException("zookeeper not connected");
}
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
本身我们项目中设置了 dubbo.consumer.timeout 但是这里的时间使用的却不是这个,而是 dubbo.registry.timeout 。由此引发了对该问题的探索,想要知道 dubbo 具体是怎么将配置文件加载的。
详解
pom dependency
在使用 dubbo-spring-boot-autoconfigure 时,会引入额外的 jar 包,以确保 自动配置能够正常识别使用。下面列出主要的:
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-autoconfigure</artifactId>
<version>2.7.8</version>
</dependency>
<!-- 自动额外引入的主要的 jar -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-autoconfigure-compatible</artifactId>
<version>2.7.8</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.spring</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
EnableDubboConfig
在 org.apache.dubbo.spring.boot.autoconfigure.DubboAutoConfiguration 类上使用了 org.apache.dubbo.config.spring.context.annotation.EnableDubboConfig 注解,该注解通过 @Import 引入了 org.apache.dubbo.config.spring.context.annotation.DubboConfigConfigurationRegistrar 类。由此开启了关于配置的一系列加载。
public class DubboConfigConfigurationRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes attributes = AnnotationAttributes.fromMap(
importingClassMetadata.getAnnotationAttributes(EnableDubboConfig.class.getName()));
boolean multiple = attributes.getBoolean("multiple");
// Single Config Bindings
registerBeans(registry, DubboConfigConfiguration.Single.class);
if (multiple) {
// Since 2.6.6 https://github.com/apache/dubbo/issues/3193
registerBeans(registry, DubboConfigConfiguration.Multiple.class);
}
// Since 2.7.6
registerCommonBeans(registry);
}
}
@EnableConfigurationBeanBindings({
@EnableConfigurationBeanBinding(prefix = "dubbo.applications", type = ApplicationConfig.class, multiple = true),
@EnableConfigurationBeanBinding(prefix = "dubbo.modules", type = ModuleConfig.class, multiple = true),
@EnableConfigurationBeanBinding(prefix = "dubbo.registries", type = RegistryConfig.class, multiple = true),
@EnableConfigurationBeanBinding(prefix = "dubbo.protocols", type = ProtocolConfig.class, multiple = true),
@EnableConfigurationBeanBinding(prefix = "dubbo.monitors", type = MonitorConfig.class, multiple = true),
@EnableConfigurationBeanBinding(prefix = "dubbo.providers", type = ProviderConfig.class, multiple = true),
@EnableConfigurationBeanBinding(prefix = "dubbo.consumers", type = ConsumerConfig.class, multiple = true),
@EnableConfigurationBeanBinding(prefix = "dubbo.config-centers", type = ConfigCenterBean.class, multiple = true),
@EnableConfigurationBeanBinding(prefix = "dubbo.metadata-reports", type = MetadataReportConfig.class, multiple = true),
@EnableConfigurationBeanBinding(prefix = "dubbo.metricses", type = MetricsConfig.class, multiple = true)
})
public static class Multiple {
}
/**
* 此处会把 com.alibaba.spring.beans.factory.annotation.EnableConfigurationBeanBindings 注解上的 @Import 引入类 com.alibaba.spring.beans.factory.annotation.ConfigurationBeanBindingsRegister 识别
*/
public static void registerBeans(BeanDefinitionRegistry registry, Class<?>... annotatedClasses) {
if (!ObjectUtils.isEmpty(annotatedClasses)) {
Set<Class<?>> classesToRegister = new LinkedHashSet(Arrays.asList(annotatedClasses));
Iterator<Class<?>> iterator = classesToRegister.iterator();
while(iterator.hasNext()) {
Class<?> annotatedClass = (Class)iterator.next();
if (isPresentBean(registry, annotatedClass)) {
iterator.remove();
}
}
AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader(registry);
if (logger.isDebugEnabled()) {
logger.debug(registry.getClass().getSimpleName() + " will register annotated classes : " + Arrays.asList(annotatedClasses) + " .");
}
reader.register((Class[])classesToRegister.toArray(com.alibaba.spring.util.ClassUtils.EMPTY_CLASS_ARRAY));
}
}
/**
* 需要注意 上述 是 Bindings 现在是 Binding .
*/
public class ConfigurationBeanBindingsRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private ConfigurableEnvironment environment;
public ConfigurationBeanBindingsRegister() {
}
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 识别出 EnableConfigurationBeanBindings 中的数组
AnnotationAttributes attributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(EnableConfigurationBeanBindings.class.getName()));
AnnotationAttributes[] annotationAttributes = attributes.getAnnotationArray("value");
// 实例化 ConfigurationBeanBindingRegistrar 去讲上面识别到的数组,一个一个注册
ConfigurationBeanBindingRegistrar registrar = new ConfigurationBeanBindingRegistrar();
registrar.setEnvironment(this.environment);
for(AnnotationAttributes element : annotationAttributes) {
// 根据注解中的值 进行初始化,并且讲配置文件或者配置中心的值赋值
registrar.registerConfigurationBeanDefinitions(element, registry);
}
}
public void setEnvironment(Environment environment) {
Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
this.environment = (ConfigurableEnvironment)environment;
}
}
ConfigurationBeanBindingRegistrar
public class ConfigurationBeanBindingRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {
static final Class ENABLE_CONFIGURATION_BINDING_CLASS = EnableConfigurationBeanBinding.class;
private static final String ENABLE_CONFIGURATION_BINDING_CLASS_NAME;
private final Log log = LogFactory.getLog(this.getClass());
private ConfigurableEnvironment environment;
public ConfigurationBeanBindingRegistrar() {
}
// ......
// 1. 调用的是 这个方法
public void registerConfigurationBeanDefinitions(Map<String, Object> attributes, BeanDefinitionRegistry registry) {
String prefix = (String)AnnotationUtils.getRequiredAttribute(attributes, "prefix");
prefix = this.environment.resolvePlaceholders(prefix);
Class<?> configClass = (Class)AnnotationUtils.getRequiredAttribute(attributes, "type");
boolean multiple = (Boolean)AnnotationUtils.getAttribute(attributes, "multiple", false);
boolean ignoreUnknownFields = (Boolean)AnnotationUtils.getAttribute(attributes, "ignoreUnknownFields", true);
boolean ignoreInvalidFields = (Boolean)AnnotationUtils.getAttribute(attributes, "ignoreInvalidFields", true);
this.registerConfigurationBeans(prefix, configClass, multiple, ignoreUnknownFields, ignoreInvalidFields, registry);
}
private void registerConfigurationBeans(String prefix, Class<?> configClass, boolean multiple, boolean ignoreUnknownFields, boolean ignoreInvalidFields, BeanDefinitionRegistry registry) {
Map<String, Object> configurationProperties = PropertySourcesUtils.getSubProperties(this.environment.getPropertySources(), this.environment, prefix);
if (CollectionUtils.isEmpty(configurationProperties)) {
if (this.log.isDebugEnabled()) {
this.log.debug("There is no property for binding to configuration class [" + configClass.getName() + "] within prefix [" + prefix + "]");
}
} else {
for(String beanName : multiple ? this.resolveMultipleBeanNames(configurationProperties) : Collections.singleton(this.resolveSingleBeanName(configurationProperties, configClass, registry))) {
// 2. 进而走到这里
this.registerConfigurationBean(beanName, configClass, multiple, ignoreUnknownFields, ignoreInvalidFields, configurationProperties, registry);
}
// 5. 注册 com.alibaba.spring.beans.factory.annotation.ConfigurationBeanBindingPostProcessor
this.registerConfigurationBindingBeanPostProcessor(registry);
}
}
private void registerConfigurationBean(String beanName, Class<?> configClass, boolean multiple, boolean ignoreUnknownFields, boolean ignoreInvalidFields, Map<String, Object> configurationProperties, BeanDefinitionRegistry registry) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(configClass);
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
// 3. 关键点 讲 source 设置为 EnableConfigurationBeanBinding
this.setSource(beanDefinition);
// 注意: 环境中的配置属性 在此处会被绑定到 attributes (Map)的 configurationProperties 中。
Map<String, Object> subProperties = this.resolveSubProperties(multiple, beanName, configurationProperties);
ConfigurationBeanBindingPostProcessor.initBeanMetadataAttributes(beanDefinition, subProperties, ignoreUnknownFields, ignoreInvalidFields);
registry.registerBeanDefinition(beanName, beanDefinition);
if (this.log.isInfoEnabled()) {
this.log.info("The configuration bean definition [name : " + beanName + ", content : " + beanDefinition + "] has been registered.");
}
}
private Map<String, Object> resolveSubProperties(boolean multiple, String beanName, Map<String, Object> configurationProperties) {
if (!multiple) {
return configurationProperties;
} else {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addLast(new MapPropertySource("_", configurationProperties));
return PropertySourcesUtils.getSubProperties(propertySources, this.environment, PropertySourcesUtils.normalizePrefix(beanName));
}
}
// 4. 将传进来的 beanDefinition 的source 设置为 com.alibaba.spring.beans.factory.annotation.EnableConfigurationBeanBinding 注解,source 在 com.alibaba.spring.beans.factory.annotation.ConfigurationBeanBindingPostProcessor 会使用到
private void setSource(AbstractBeanDefinition beanDefinition) {
beanDefinition.setSource(ENABLE_CONFIGURATION_BINDING_CLASS);
}
private void registerConfigurationBindingBeanPostProcessor(BeanDefinitionRegistry registry) {
BeanRegistrar.registerInfrastructureBean(registry, "configurationBeanBindingPostProcessor", ConfigurationBeanBindingPostProcessor.class);
}
// ......
private Set<String> resolveMultipleBeanNames(Map<String, Object> properties) {
Set<String> beanNames = new LinkedHashSet();
for(String propertyName : properties.keySet()) {
int index = propertyName.indexOf(".");
if (index > 0) {
String beanName = propertyName.substring(0, index);
beanNames.add(beanName);
}
}
return beanNames;
}
private String resolveSingleBeanName(Map<String, Object> properties, Class<?> configClass, BeanDefinitionRegistry registry) {
String beanName = (String)properties.get("id");
if (!StringUtils.hasText(beanName)) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(configClass);
beanName = BeanDefinitionReaderUtils.generateBeanName(builder.getRawBeanDefinition(), registry);
}
return beanName;
}
static {
ENABLE_CONFIGURATION_BINDING_CLASS_NAME = ENABLE_CONFIGURATION_BINDING_CLASS.getName();
}
}
ConfigurationBeanBindingPostProcessor
该类是实现了 org.springframework.beans.factory.config.BeanPostProcessor ,所以会在实例前触发方法,postProcessBeforeInitialization 。
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
BeanDefinition beanDefinition = this.getNullableBeanDefinition(beanName);
// 这里就是看 beanDefinition 对象中的 source 属性是不是之前的 EnableConfigurationBeanBinding,是的话就直接进行属性绑定以及加载。
if (this.isConfigurationBean(bean, beanDefinition)) {
this.bindConfigurationBean(bean, beanDefinition);
this.customize(beanName, bean);
}
return bean;
}
private boolean isConfigurationBean(Object bean, BeanDefinition beanDefinition) {
return beanDefinition != null && ConfigurationBeanBindingRegistrar.ENABLE_CONFIGURATION_BINDING_CLASS.equals(beanDefinition.getSource()) && ObjectUtils.nullSafeEquals(this.getBeanClassName(bean), beanDefinition.getBeanClassName());
}
private void bindConfigurationBean(Object configurationBean, BeanDefinition beanDefinition) {
Map<String, Object> configurationProperties = getConfigurationProperties(beanDefinition);
boolean ignoreUnknownFields = getIgnoreUnknownFields(beanDefinition);
boolean ignoreInvalidFields = getIgnoreInvalidFields(beanDefinition);
// getConfigurationBeanBinder() 的结果为 org.apache.dubbo.spring.boot.autoconfigure.BinderDubboConfigBinder 实例
this.getConfigurationBeanBinder().bind(configurationProperties, ignoreUnknownFields, ignoreInvalidFields, configurationBean);
if (this.log.isInfoEnabled()) {
this.log.info("The configuration bean [" + configurationBean + "] have been binding by the configuration properties [" + configurationProperties + "]");
}
}
/**
* 次数运用到了spring的属性绑定功能,不再深入细究。
*/
class BinderDubboConfigBinder implements ConfigurationBeanBinder {
@Override
public void bind(Map<String, Object> configurationProperties, boolean ignoreUnknownFields,
boolean ignoreInvalidFields, Object configurationBean) {
Iterable<PropertySource<?>> propertySources = asList(new MapPropertySource("internal", configurationProperties));
// Converts ConfigurationPropertySources
Iterable<ConfigurationPropertySource> configurationPropertySources = from(propertySources);
// Wrap Bindable from DubboConfig instance
Bindable bindable = Bindable.ofInstance(configurationBean);
Binder binder = new Binder(configurationPropertySources, new PropertySourcesPlaceholdersResolver(propertySources));
// Get BindHandler
BindHandler bindHandler = getBindHandler(ignoreUnknownFields, ignoreInvalidFields);
// Bind
binder.bind("", bindable, bindHandler);
}
private BindHandler getBindHandler(boolean ignoreUnknownFields,
boolean ignoreInvalidFields) {
BindHandler handler = BindHandler.DEFAULT;
if (ignoreInvalidFields) {
handler = new IgnoreErrorsBindHandler(handler);
}
if (!ignoreUnknownFields) {
UnboundElementsSourceFilter filter = new UnboundElementsSourceFilter();
handler = new NoUnboundElementsBindHandler(handler, filter);
}
return handler;
}
}
到这里,属性绑定就成功完成了。
问题源码跟踪
说会一开始出现的问题,主要问题是 在启动的时候回去连接 zk ,启动是由 org.apache.dubbo.config.spring.context.DubboBootstrapApplicationListener#onApplicationContextEvent 触发 org.apache.dubbo.config.bootstrap.DubboBootstrap#start 方法,监听 spring 声明周期事件。
- org.apache.dubbo.config.bootstrap.DubboBootstrap#initialize
- org.apache.dubbo.config.bootstrap.DubboBootstrap#startConfigCenter
- org.apache.dubbo.config.bootstrap.DubboBootstrap#useRegistryAsConfigCenterIfNecessary(额外说明一下该方法,代码如下:)
- org.apache.dubbo.config.bootstrap.DubboBootstrap#prepareEnvironment
- org.apache.dubbo.common.config.configcenter.AbstractDynamicConfigurationFactory#getDynamicConfiguration
- org.apache.dubbo.common.config.configcenter.AbstractDynamicConfigurationFactory#createDynamicConfiguration
- org.apache.dubbo.configcenter.support.zookeeper.ZookeeperDynamicConfigurationFactory#createDynamicConfiguration (这里根据不同的实现类去到不同的方法,笔者这里是 zk)
- org.apache.dubbo.remoting.zookeeper.ZookeeperTransporter#connect
- org.apache.dubbo.remoting.zookeeper.curator.CuratorZookeeperTransporter#createZookeeperClient
- org.apache.dubbo.remoting.zookeeper.curator.CuratorZookeeperClient#CuratorZookeeperClient
- org.apache.curator.framework.CuratorFramework#blockUntilConnected(int, java.util.concurrent.TimeUnit)
private void useRegistryAsConfigCenterIfNecessary() {
// we use the loading status of DynamicConfiguration to decide whether ConfigCenter has been initiated.
if (environment.getDynamicConfiguration().isPresent()) {
return;
}
if (CollectionUtils.isNotEmpty(configManager.getConfigCenters())) {
return;
}
configManager
.getDefaultRegistries()
.stream()
.filter(this::isUsedRegistryAsConfigCenter)
.map(this::registryAsConfigCenter)
.forEach(configManager::addConfigCenter);
}
public List<RegistryConfig> getDefaultRegistries() {
return getDefaultConfigs(getConfigsMap(getTagName(RegistryConfig.class)));
}
从 useRegistryAsConfigCenterIfNecessary 方法中可以看出,启动时获取的是 RegistryConfig 的配置,所以我们 consumer.timeout 是不生效的。
总结
Dubbo 其实区分了很多的配置,这些配置也有默认值,在出现问题的时候可以查看一下自己的配置,有些关于时间的配置可以适当的延长,毕竟网络是不稳定的。
希望大家生活愉快!!