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());
    }

 

posted on 2021-08-25 16:53  砌码匠人  阅读(842)  评论(0编辑  收藏  举报

导航