• 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());
        }
  • 相关阅读:
    cygwin配合NDK开发Android程序
    和菜鸟一起学c之函数指针
    和菜鸟一起学android4.0.3源码之SD卡U盘等自动挂载配置
    Android系统的开机画面显示过程分析
    android编译系统的makefile文件Android.mk写法
    Linux下makefile教程
    和菜鸟一起学linux之本地git中心仓库建立
    强人总结的Windows XP实用技巧45条(一)
    Webshell下自动挂马的ASP
    多进程Telnet的木马例子
  • 原文地址:https://www.cnblogs.com/reboot30/p/15092822.html
Copyright © 2020-2023  润新知