dubbo 服务暴露
首先需要澄清的是,服务暴露与服务注册是两个概念。在Spring Cloud Alibaba dubbo中服务暴露是在本地维护一个服务列表(具体的一个个接口服务),
服务注册是将本服务(整个微服务)的项目名称及对应IP、port注册到注册中心,服务消费方从注册中心拉取微服务列表,然后根据ip、port去对应服务机器上拉取暴露的接口服务列表。
这里会有个检查,消费方reference的接口服务如果在提供方的机器上未暴露,就报异常。
进入正题,在service解析那篇博文中提到的org.apache.dubbo.config.spring.beans.factory.annotation.ServiceClassPostProcessor后处理bean的postProcessBeanDefinitionRegistry
方法开始处添加了DubboBootstrapApplicationListener监听器处理ApplicationContextEvent。值得一提的是,在dubbo springboot自动配置包的org.apache.dubbo.spring.boot.autoconfigure.DubboAutoConfiguration
中setApplicationContext也给context添加了该监听器,好在监听器处理事件时启动org.apache.dubbo.config.bootstrap.DubboBootstrap限制了只能启动一次。
org.apache.dubbo.config.spring.context.DubboBootstrapApplicationListener#onApplicationContextEvent
@Override public void onApplicationContextEvent(ApplicationContextEvent event) { if (event instanceof ContextRefreshedEvent) {
// ContextRefreshedEvent事件时启动dubbo onContextRefreshedEvent((ContextRefreshedEvent) event); } else if (event instanceof ContextClosedEvent) { onContextClosedEvent((ContextClosedEvent) event); } } private void onContextRefreshedEvent(ContextRefreshedEvent event) {
// 启动dubbo dubboBootstrap.start(); } private void onContextClosedEvent(ContextClosedEvent event) { dubboBootstrap.stop(); }
org.apache.dubbo.config.bootstrap.DubboBootstrap#start
public DubboBootstrap start() {
// 限制只能启动一次 if (started.compareAndSet(false, true)) { ready.set(false);
// 初始化,后面另开篇幅讲 initialize(); if (logger.isInfoEnabled()) { logger.info(NAME + " is starting..."); } // 1. export Dubbo Services
// 暴露服务,本文的重点 exportServices(); // Not only provider register if (!isOnlyRegisterProvider() || hasExportedServices()) { // 2. export MetadataService exportMetadataService(); //3. Register the local ServiceInstance if required registerServiceInstance(); } // 消费端引用服务,后面另开博文讲 referServices();
// 异步导出的话就等所有future都返回后设置ready状态 if (asyncExportingFutures.size() > 0) { new Thread(() -> { try { this.awaitFinish(); } catch (Exception e) { logger.warn(NAME + " exportAsync occurred an exception."); } ready.set(true); if (logger.isInfoEnabled()) { logger.info(NAME + " is ready."); } }).start(); } else { ready.set(true); if (logger.isInfoEnabled()) { logger.info(NAME + " is ready."); } } if (logger.isInfoEnabled()) { logger.info(NAME + " has started."); } } return this; }
下面我们重点看下exportServices:
private void exportServices() {
// 获取所有ServiceConfig并调用export()方法 configManager.getServices().forEach(sc -> { // TODO, compatible with ServiceConfig.export() ServiceConfig serviceConfig = (ServiceConfig) sc; serviceConfig.setBootstrap(this); if (exportAsync) { ExecutorService executor = executorRepository.getServiceExporterExecutor(); Future<?> future = executor.submit(() -> { sc.export(); exportedServices.add(sc); }); asyncExportingFutures.add(future); } else { sc.export(); exportedServices.add(sc); } }); }
那么问题来了,上面的configManager中ServiceConfig是什么时候加进去的呢?通过静态代码分析活断点调试可知在ServiceConfig的父类方法org.apache.dubbo.config.AbstractConfig#addIntoConfigManager中添加。
下面看下export逻辑,org.apache.dubbo.config.ServiceConfig#export =》org.apache.dubbo.config.ServiceConfig#doExport =》org.apache.dubbo.config.ServiceConfig#doExportUrls
private void doExportUrls() { ServiceRepository repository = ApplicationModel.getServiceRepository(); ServiceDescriptor serviceDescriptor = repository.registerService(getInterfaceClass()); repository.registerProvider( getUniqueServiceName(), ref, serviceDescriptor, this, serviceMetadata ); // 获取所有注册中心URL,这里会将URL的Protocol设置为registry,这是后面适配具体的PROTOCOL.export的依据,
// 对应的就是org.apache.dubbo.registry.integration.RegistryProtocol#export List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true); // 循环所有协议,这里也是dubbo支持多协议多注册中心的原理所在 for (ProtocolConfig protocolConfig : protocols) { String pathKey = URL.buildKey(getContextPath(protocolConfig) .map(p -> p + "/" + path) .orElse(path), group, version); // In case user specified path, register service one more time to map it to path. repository.registerService(pathKey, interfaceClass); // TODO, uncomment this line once service key is unified serviceMetadata.setServiceKey(pathKey); doExportUrlsFor1Protocol(protocolConfig, registryURLs); } }
org.apache.dubbo.config.ServiceConfig#doExportUrlsFor1Protocol中重点看如下地方:
org.apache.dubbo.registry.integration.RegistryProtocol#export:
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException { URL registryUrl = getRegistryUrl(originInvoker); // url to export locally
// 服务接口地址,用于本地创建server处理接口调用请求 URL providerUrl = getProviderUrl(originInvoker); // Subscribe the override data // FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call // the same service. Because the subscribed is cached key with the name of the service, it causes the // subscription information to cover. final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl); final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker); overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener); providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener); //export invoker
// 这里创建的NettyServer,默认为dubbo协议,通过org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#export暴露
// dubbo处理Netty网络请求部分后面单独写一遍博文 final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl); // url to registry final Registry registry = getRegistry(originInvoker); final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl); // decide if we need to delay publish boolean register = providerUrl.getParameter(REGISTER_KEY, true); if (register) {
// 注册,代码见下方 register(registryUrl, registeredProviderUrl); } // register stated url on provider model registerStatedUrl(registryUrl, registeredProviderUrl, register); exporter.setRegisterUrl(registeredProviderUrl); exporter.setSubscribeUrl(overrideSubscribeUrl); // Deprecated! Subscribe to override rules in 2.6.x or before. registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener); notifyExport(exporter); //Ensure that a new exporter instance is returned every time export return new DestroyableExporter<>(exporter); }
private void register(URL registryUrl, URL registeredProviderUrl) {
// spring could alibaba dubbo对应注册中心的协议为spring-cloud
// 所以这里对应的是com.alibaba.cloud.dubbo.registry.SpringCloudRegistryFactory#getRegistry
// 该方法实际为父类方法,核心是调用子类的createRegistry方法创建registry Registry registry = registryFactory.getRegistry(registryUrl); registry.register(registeredProviderUrl); }
com.alibaba.cloud.dubbo.registry.SpringCloudRegistryFactory#createRegistry:
public Registry createRegistry(URL url) { init(); DubboCloudProperties dubboCloudProperties = applicationContext .getBean(DubboCloudProperties.class); Registry registry = null; // 根据dubbo.cloud配置的registryType创建对应对象,默认为DubboCloudRegistry switch (dubboCloudProperties.getRegistryType()) { case SPRING_CLOUD_REGISTRY_PROPERTY_VALUE: registry = new SpringCloudRegistry(url, discoveryClient, dubboServiceMetadataRepository, dubboMetadataConfigServiceProxy, jsonUtils, dubboGenericServiceFactory, applicationContext); break; default: registry = new DubboCloudRegistry(url, discoveryClient, dubboServiceMetadataRepository, dubboMetadataConfigServiceProxy, jsonUtils, dubboGenericServiceFactory, applicationContext); break; } return registry; }
回到上面的org.apache.dubbo.registry.integration.RegistryProtocol#register方法,调用registry的register方法,这里就是DubboCloudRegistry.register(),最终调用com.alibaba.cloud.dubbo.registry.DubboCloudRegistry#doRegister方法:
repository实际就是维护了一个allExportedURLs暴露的接口服务列表供消费方查询:
com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository#exportURL
上面暴露过程看完了,由上面得知,dubbo.registry. address配置的spring-cloud协议时,表明将dubbo的注册中心指向spring cloud,DubboCloudRegistry仅仅是在本地维护服务接口列表,那么spring cloud的注册中心SDK又是怎么注册到server的呢?依托的是dubbo cloud的自动配置类com.alibaba.cloud.dubbo.autoconfigure.DubboServiceRegistrationNonWebApplicationAutoConfiguration
ApplicationStartedEvent事件是在spring boot启动完容器后发布的:
org.springframework.boot.SpringApplication#run(java.lang.String...)
对应事件发布listener的started方法org.springframework.boot.context.event.EventPublishingRunListener#started
这里有个时序的问题,本地export服务是在org.springframework.context.support.AbstractApplicationContext#finishRefresh时发布的ContextRefreshedEvent事件时处理,注册到注册中心是在此之后,因为如果先注册到注册中心后,消费方来服务方机器拉取暴露的服务列表时会check,如果没有对应服务就抛出异常。而下方要说的nacos本身的自动注册是在org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#finishRefresh方法中:
回到上面注册到注册中心的地方,org.springframework.cloud.client.serviceregistry.ServiceRegistry就是spring cloud规范中服务注册标准接口,这样就能把dubbo和spring cloud联系起来了,以nacos为注册中心,spring-cloud-starter-alibaba-nacos-discovery为客户端SDK,该starter中对应的ServiceRegistry实现就是com.alibaba.cloud.nacos.registry.NacosServiceRegistry。
通过引入spring-cloud-starter-dubbo和spring-cloud-starter-alibaba-nacos-discovery就可以把spring cloud、dubbo、nacos三者联系起来了。
因为Spring Cloud Alibaba Dubbo和Spring Cloud Alibaba Nacos都是为了靠拢Spring Cloud生态,作为原生dubbo和nacos与Spring Cloud的桥梁,实现Spring Cloud的标准接口,两者均可单独集成,dubbo的这个服务自动注册和spring-cloud-starter-alibaba-nacos-discovery中自带的自动注册类似,com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration最终也是通过com.alibaba.cloud.nacos.registry.NacosServiceRegistry实现服务注册。
使用方法是配置spring.cloud.service-registry.auto-registration.enabled=true或者启动类加EnableDiscoveryClient注解,autoRegister配置为true,
对应selector就会加上“org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration”
public String[] selectImports(AnnotationMetadata metadata) { String[] imports = super.selectImports(metadata); AnnotationAttributes attributes = AnnotationAttributes.fromMap( metadata.getAnnotationAttributes(getAnnotationClass().getName(), true)); boolean autoRegister = attributes.getBoolean("autoRegister"); // 自动注册,即为把当前微服务注册到注册中心 if (autoRegister) { List<String> importsList = new ArrayList<>(Arrays.asList(imports));
// 添加自动注册类,到时候会生成对应bean importsList.add( "org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration"); imports = importsList.toArray(new String[0]); } else { Environment env = getEnvironment(); if (ConfigurableEnvironment.class.isInstance(env)) { ConfigurableEnvironment configEnv = (ConfigurableEnvironment) env; LinkedHashMap<String, Object> map = new LinkedHashMap<>();
// 不自动注册 map.put("spring.cloud.service-registry.auto-registration.enabled", false); MapPropertySource propertySource = new MapPropertySource( "springCloudDiscoveryClient", map); configEnv.getPropertySources().addLast(propertySource); } } return imports; }
以上configuration会触发com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration自动配置:
// 自动创建NacosAutoServiceRegistration到IOC容器,该类父类AbstractAutoServiceRegistration是
// spring cloud自动服务注册标准类,实现了ApplicationListener接口,监听WebServerInitializedEvent事件,
// 当应用初始化完成后调用bind方法,最终还是通过这里构建时候传入的NacosServiceRegistry实现注册
@Bean @ConditionalOnBean(AutoServiceRegistrationProperties.class) public NacosAutoServiceRegistration nacosAutoServiceRegistration( NacosServiceRegistry registry, AutoServiceRegistrationProperties autoServiceRegistrationProperties, NacosRegistration registration) { return new NacosAutoServiceRegistration(registry, autoServiceRegistrationProperties, registration); }
org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration#register:
protected void register() { this.serviceRegistry.register(getRegistration()); }