【Dubbo源码二:Dubbo服务导出】

入口

Dubbo服务导出的入口:
image.png
image.png
服务导出是在DubboBootstrapApplicationListener在监听到ApplicationContextEvent的ContextRefreshedEvent事件后,会触发dubboBootstrap.start(), 在这个方法中最后会导出Dubbo服务

DubboBootstrapApplicationListener

DubboBootstrapApplicationListener是哪里注册进来的那?

  1. 入口一:在扫描@DubboService的ServiceClassPostProcessor 里面会注册一个Dubbo的监听器

image.png

  1. 入口二:在解析@DubboReference的注解的时候,调用registerCommonBeans的时候,向Spring容器注册了一个类DubboApplicationListenerRegistrar

image.png

DubboBootstrap.exportServices

image.png
configManager就是一个配置缓存类,里面放的都是之前的配置
image.png

  1. configManager.getServices() 获取的是service,将所有的service转换成serviceBean
  2. 调用serviceBean.export():serviceBean继承的是serviceConfig,最终会调用到serviceConfig.export

ServiceConfig.export

image.png

  1. 判断bootstrap是否初始化,如果没有初始化,需要初始化
  2. 检查并更新服务的参数:checkAndUpdateSubConfigs�
 private void checkAndUpdateSubConfigs() {
        // Use default configs defined explicitly with global scope
        completeCompoundConfigs();
        checkDefault();
        checkProtocol();
        // init some null configuration.
        List<ConfigInitializer> configInitializers = ExtensionLoader.getExtensionLoader(ConfigInitializer.class)
                .getActivateExtension(URL.valueOf("configInitializer://"), (String[]) null);
        configInitializers.forEach(e -> e.initServiceConfig(this));

        // if protocol is not injvm checkRegistry
        if (!isOnlyInJvm()) {
            checkRegistry();
        }
        this.refresh();

        if (StringUtils.isEmpty(interfaceName)) {
            throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
        }
        // 检测 ref 是否为泛化服务类型
        if (ref instanceof GenericService) {
            interfaceClass = GenericService.class;
            if (StringUtils.isEmpty(generic)) {
                generic = Boolean.TRUE.toString();
            }
        } else {
            try {
                interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                        .getContextClassLoader());
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            // 对 interfaceClass,以及 <dubbo:method> 标签中的必要字段进行检查
            checkInterfaceAndMethods(interfaceClass, getMethods());
            checkRef();
            generic = Boolean.FALSE.toString();
        }
        // local 和 stub 在功能应该是一致的,用于配置本地存根
        if (local != null) {
            if ("true".equals(local)) { // 如果配置的事true,那么默认类名就是 接口名拼接Local
                local = interfaceName + "Local";
            }
            Class<?> localClass;
            try {
                // 获取本地存根类
                localClass = ClassUtils.forNameWithThreadContextClassLoader(local);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            if (!interfaceClass.isAssignableFrom(localClass)) {
                throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
            }
        }
        // 逻辑同上
        if (stub != null) {
            if ("true".equals(stub)) {
                stub = interfaceName + "Stub";
            }
            Class<?> stubClass;
            try {
                stubClass = ClassUtils.forNameWithThreadContextClassLoader(stub);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            if (!interfaceClass.isAssignableFrom(stubClass)) {
                throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + interfaceName);
            }
        }
        checkStubAndLocal(interfaceClass);
        ConfigValidationUtils.checkMock(interfaceClass, this);
        ConfigValidationUtils.validateServiceConfig(this);
        postProcessConfig();
    }
  1. 检查ServiceConfig的配置,如果ServiceConfig中的某些属性如果是空的,那么就从AbstractInterfaceConfig、ModuleConfig、ApplicationConfig、ProviderConfig中获取并赋值给ServiceConfig对象中对应的属性。

  2. 检查provider属性值是否为空,如果为空的话,创建一个新的ProviderConfig并赋值给他。

  3. 检查协议,如果没有单独的配置protocols,则从provider获取配置的协议并赋值给ServiceConfig对应的属性。

  4. 如果配置中心的全局配置或应用配置中也配置了一个协议,那么就会被添加到ServiceConfig中。

  5. 如果protocol协议配置的不是只有injvm协议,那么就需要把服务注册到注册中心里去。

  6. 检查注册中心的配置,如果没有配置的话,从application对象里获取

  7. 刷新ServiceConfig配置

  8. 检查当前服务是否为泛化服务

  9. 检查Stub、Local、Mock

  10. 检验ServiceConfig配置的值是否合法,长度、非法字符等

  11. 通过SPI调用ConfigPostProcessor实现类,进行配置的后置处理

  12. 完善serviceMetadata的参数:version、group、defaultGroup、serviceInterfaceName、serviceType、target等属性值

  13. 检查是否应该被导出。

  14. 如果是延迟暴露,则使用线程池任务调用doExport方法导出。

  15. 如果是正常导出,直接调用doExport方法。

  16. 发送导出完成事件。

ServiceConfig.doExport

image.png

ServiceConfig.doExportUrls

image.png
讲讲这里做了几件事:

  1. 把接口、具体的实现类、ServiceBean、ServiceDescriptor注册到本地仓库的provider

image.png

  1. 查询所有的注册中心,构造出注册中心URL

image.png

  1. 遍历所有协议,将每个协议都进行服务导出 doExportUrlFor1Protocol
 private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {

        String name = protocolConfig.getName();
        if (StringUtils.isEmpty(name)) {
            // 如果协议名为空,或空串,则将协议名变量设置为 dubbo
            name = DUBBO;
        }

        Map<String, String> map = new HashMap<String, String>();
        // 添加 side、版本、时间戳以及进程号等信息到 map 中
        map.put(SIDE_KEY, PROVIDER_SIDE);

        ServiceConfig.appendRuntimeParameters(map);
        AbstractConfig.appendParameters(map, getMetrics());
        // 通过反射将对象的字段信息添加到 map 中
        AbstractConfig.appendParameters(map, getApplication());
        AbstractConfig.appendParameters(map, getModule());
        // remove 'default.' prefix for configs from ProviderConfig
        // appendParameters(map, provider, Constants.DEFAULT_KEY);
        AbstractConfig.appendParameters(map, provider);
        AbstractConfig.appendParameters(map, protocolConfig);
        AbstractConfig.appendParameters(map, this);
        MetadataReportConfig metadataReportConfig = getMetadataReportConfig();
        if (metadataReportConfig != null && metadataReportConfig.isValid()) {
            map.putIfAbsent(METADATA_KEY, REMOTE_METADATA_STORAGE_TYPE);
        }
        if (CollectionUtils.isNotEmpty(getMethods())) {
            // methods 为 MethodConfig 集合,MethodConfig 中存储了 <dubbo:method> 标签的配置信息
            for (MethodConfig method : getMethods()) {
                // 添加 MethodConfig 对象的字段信息到 map 中,键 = 方法名.属性名。
                // 比如存储 <dubbo:method name="sayHello" retries="2"> 对应的 MethodConfig,
                // 键 = sayHello.retries,map = {"sayHello.retries": 2, "xxx": "yyy"}
                AbstractConfig.appendParameters(map, method, method.getName());
                String retryKey = method.getName() + ".retry";
                if (map.containsKey(retryKey)) {
                    String retryValue = map.remove(retryKey);
                    // 检测 MethodConfig retry 是否为 false,若是,则设置重试次数为0
                    if ("false".equals(retryValue)) {
                        map.put(method.getName() + ".retries", "0");
                    }
                }
                // 获取 ArgumentConfig 列表
                List<ArgumentConfig> arguments = method.getArguments();
                if (CollectionUtils.isNotEmpty(arguments)) {
                    for (ArgumentConfig argument : arguments) {
                        // convert argument type
                        // 分支1  检测 type 属性是否为空,或者空串
                        if (argument.getType() != null && argument.getType().length() > 0) {
                            Method[] methods = interfaceClass.getMethods();
                            // visit all methods
                            if (methods.length > 0) {
                                for (int i = 0; i < methods.length; i++) {
                                    String methodName = methods[i].getName();
                                    // target the method, and get its signature
                                    // 1、比对方法名,查找目标方法 2、通过反射获取目标方法的参数类型数组 argtypes
                                    if (methodName.equals(method.getName())) {
                                        Class<?>[] argtypes = methods[i].getParameterTypes();
                                        // one callback in the method
                                        if (argument.getIndex() != -1) {
                                            // 检测 ArgumentConfig 中的 type 属性与方法参数列表
                                            // 分支2   中的参数名称是否一致,不一致则抛出异常
                                            //1. 从 argtypes 数组中获取下标 index 处的元素 argType
                                            //2. 检测 argType 的名称与 ArgumentConfig 中的 type 属性是否一致
                                            //3. 添加 ArgumentConfig 字段信息到 map 中,或抛出异常
                                            if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
                                                // 添加 ArgumentConfig 字段信息到 map 中,
                                                // 键前缀 = 方法名.index,比如:
                                                // map = {"sayHello.3": true}
                                                AbstractConfig.appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                                            } else {
                                                throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
                                            }
                                        } else { // 分支3 ⭐️
                                            //1. 遍历参数类型数组 argtypes,查找 argument.type 类型的参数
                                            //2. 添加 ArgumentConfig 字段信息到 map 中
                                            // multiple callbacks in the method
                                            for (int j = 0; j < argtypes.length; j++) {
                                                Class<?> argclazz = argtypes[j];
                                                // 从参数类型列表中查找类型名称为 argument.type 的参数
                                                if (argclazz.getName().equals(argument.getType())) {
                                                    AbstractConfig.appendParameters(map, argument, method.getName() + "." + j);
                                                    if (argument.getIndex() != -1 && argument.getIndex() != j) {
                                                        throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        } else if (argument.getIndex() != -1) {
                            // 用户未配置 type 属性,但配置了 index 属性,且 index != -1
                            // 分支4 ⭐️
                            // 添加 ArgumentConfig 字段信息到 map 中
                            AbstractConfig.appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                        } else {
                            throw new IllegalArgumentException("Argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");
                        }

                    }
                }
            } // end of methods for
        }

        // 检测 generic 是否为 "true",并根据检测结果向 map 中添加不同的信息
        if (ProtocolUtils.isGeneric(generic)) {
            map.put(GENERIC_KEY, generic);
            map.put(METHODS_KEY, ANY_VALUE);
        } else {
            String revision = Version.getVersion(interfaceClass, version);
            if (revision != null && revision.length() > 0) {
                map.put(REVISION_KEY, revision);
            }
            /**
             * 为接口生成包裹类 Wrapper ,Wrapper就是interface的一个动态代理,类似于 mybatis的mapper也会生成一个
             * 只不过Dubbo用的是自己的动态代理,mybatis用的是JDK动态代理
             */
            String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
            if (methods.length == 0) {
                // 添加方法名到 map 中,如果包含多个方法名,则用逗号隔开,比如 method = init,destroy
                logger.warn("No method found in service interface " + interfaceClass.getName());
                map.put(METHODS_KEY, ANY_VALUE);
            } else {
                // 将逗号作为分隔符连接方法名,并将连接后的字符串放入 map 中
                // ->   haveNoReturn,setTestgaga,getTestddd,hello
                map.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
            }
        }

        /**
         * Here the token value configured by the provider is used to assign the value to ServiceConfig#token
         */
        if (ConfigUtils.isEmpty(token) && provider != null) {
            token = provider.getToken();
        }

        // 添加 token 到 map 中
        if (!ConfigUtils.isEmpty(token)) {
            if (ConfigUtils.isDefault(token)) {
                // 随机生成 token
                map.put(TOKEN_KEY, UUID.randomUUID().toString());
            } else {
                map.put(TOKEN_KEY, token);
            }
        }
        //init serviceMetadata attachments
        serviceMetadata.getAttachments().putAll(map);

        /**
         * List<URL> registryURLs
         * 单个 URL -> registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=demo-provider
         *              &dubbo=2.0.2&id=registry1&mapping-type=metadata&mapping.type=metadata&pid=10000&qos.port=22222
         *              &registry=zookeeper&timestamp=1611892414279
         * 由此分析呢:协议的路径 就是执行动作的类
         */
        // export IP 获取 本地主机的IP,也就是要暴露接口服务所属主机的IP地址(就是netty或者tomcat绑定的 IP )
        String host = findConfigedHosts(protocolConfig, registryURLs, map);
        // export port 获取 暴露的端口,也就是要暴露接口服务 所属 服务的 端口(就是netty或者tomcat绑定的 端口),默认20880
        Integer port = findConfigedPorts(protocolConfig, name, map);

        // 组装 URL
        // getContextPath(protocolConfig) 获取的就是 url的path
        // 成员变量path 就是 接口名 -> org.apache.dubbo.demo.GreetingService
        // 所以最终组装的path就是 contextPath+"/"+path ,但是这里contextPath为空
        // url = dubbo://192.168.1.103:20880/org.apache.dubbo.demo.GreetingService?anyhost=true&application=demo-provider
        //            &bind.ip=192.168.1.103&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=greeting
        //            &interface=org.apache.dubbo.demo.GreetingService&mapping-type=metadata&mapping.type=metadata&metadata-type=remote
        //            &methods=haveNoReturn,setTestgaga,getTestddd,hello&pid=1144&qos.port=22222&release=&revision=1.0.0
        //            &side=provider&timeout=5000&timestamp=1611924667311&version=1.0.0
        URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);

        /**
         * 前置工作做完,接下来就可以进行服务导出了。服务导出分为导出到本地 (JVM),和导出到远程
         */
        // You can customize Configurator to append extra parameters
        if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                .hasExtension(url.getProtocol())) { // 没有自定义的话,就不会进这个判断
            // 加载 ConfiguratorFactory,并生成 Configurator 实例,然后通过实例配置 url
            url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                    .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
        }

        String scope = url.getParameter(SCOPE_KEY);
        // don't export when none is configured
        // scope必须不是 none,如果 scope = none,则什么都不做,
        if (!SCOPE_NONE.equalsIgnoreCase(scope)) {
            // export to local if the config is not remote (export to remote only when config is remote)
            if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {// scope != remote,导出到本地
                /**
                 *  scope = none,不导出服务
                 *  scope != remote,导出到本地
                 *  scope != local,导出到远程
                 * 不管是导出到本地,还是远程。进行服务导出之前,均需要先创建 Invoker
                 * Invoker 是一个非常重要的模型。在服务提供端,以及服务引用端均会出现 Invoker
                 * Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,
                 * 可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。
                 */
                exportLocal(url);
            }
            // export to remote if the config is not local (export to local only when config is local)
            if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {  // scope != local,导出到远程
                if (CollectionUtils.isNotEmpty(registryURLs)) {
                    for (URL registryURL : registryURLs) {
                        /**
                         * List<URL> registryURLs
                         * 单个 URL -> registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=demo-provider
                         *              &dubbo=2.0.2&id=registry1&mapping-type=metadata&mapping.type=metadata&pid=10000&qos.port=22222
                         *              &registry=zookeeper&timestamp=1611892414279
                         * 由此分析呢:协议的路径 就是执行动作的类
                         */
                        //if protocol is only injvm ,not register
                        if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
                            continue;
                        }
                        url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
                        // 加载监视器链接,一般为空
                        URL monitorUrl = ConfigValidationUtils.loadMonitor(this, registryURL);
                        if (monitorUrl != null) {
                            // 将监视器链接作为参数添加到 url 中
                            url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
                        }
                        if (logger.isInfoEnabled()) {
                            if (url.getParameter(REGISTER_KEY, true)) {
                                logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
                            } else {
                                logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                            }
                        }
                        // url = dubbo://192.168.1.103:20880/org.apache.dubbo.demo.GreetingService?anyhost=true&application=demo-provider
                        //            &bind.ip=192.168.1.103&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=greeting
                        //            &interface=org.apache.dubbo.demo.GreetingService&mapping-type=metadata&mapping.type=metadata&metadata-type=remote
                        //            &methods=haveNoReturn,setTestgaga,getTestddd,hello&pid=1144&qos.port=22222&release=&revision=1.0.0
                        //            &side=provider&timeout=5000&timestamp=1611924667311&version=1.0.0
                        // For providers, this is used to enable custom proxy to generate invoker
                        // 看上面英文注释!!,如果自定义代理工厂的话,才会有这个key
                        String proxy = url.getParameter(PROXY_KEY);
                        if (StringUtils.isNotEmpty(proxy)) {
                            registryURL = registryURL.addParameter(PROXY_KEY, proxy);
                        }
                        /**
                         *  为服务提供类(ref)生成 Invoker
                         *  Invoker 是由 ProxyFactory 创建而来,Dubbo 默认的 ProxyFactory 实现类是 JavassistProxyFactory
                         *  ref   -> GreetingServiceImpl    接口实现类
                         *  interfaceClass -> GreetingService  接口
                         *  registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()) -> :
                         *              EXPORT_KEY -> "export"
                         *              url.toFullString() -> 就是url的全路径:dubbo://192.168.1.103:20880/org.apache.dubbo.demo.GreetingService?anyhost=true......
                         *                                    这个url全路径包含了服务的IP地址,端口,接口名,方法名数组等等
                         *  registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()) 目的就是把需要暴露的服务 encode成一个value
                         *  然后作为一个export参数,拼接到registryURL后面,形如:
                         *  registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?.....&export=经过encode的完整服务
                         *  与上面exportLocal(...)对比着看
                         */
                        Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
                        // DelegateProviderMetaDataInvoker 用于持有 Invoker 和 ServiceConfig
                        DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
                        /**
                         * 导出服务,并生成 Exporter
                         * 导出服务到本地相比,导出服务到远程的过程要复杂不少,
                         * 其包含了服务导出与服务注册两个过程
                         * PROTOCOL.export(wrapperInvoker) 这行代码呢,根据dubbo-spi机制,会先 url= wrapperInvoker.getUrl(),
                         * 然后 Protocol p = url.getProtocol(),根据duboo-spi的 wrapper机制
                         * p = ProtocolFilterWrapper(ProtocolListenerWrapper(RegistryProtocol))
                         */
                        Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
                        exporters.add(exporter);
                    }
                } else { // 不存在注册中心,仅导出服务
                    // url = dubbo://192.168.1.103:20880/org.apache.dubbo.demo.GreetingService?anyhost=true&application=demo-provider
                    //            &bind.ip=192.168.1.103&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=greeting
                    //            &interface=org.apache.dubbo.demo.GreetingService&mapping-type=metadata&mapping.type=metadata&metadata-type=remote
                    //            &methods=haveNoReturn,setTestgaga,getTestddd,hello&pid=1144&qos.port=22222&release=&revision=1.0.0
                    //            &side=provider&timeout=5000&timestamp=1611924667311&version=1.0.0
                    // For providers, this is used to enable custom proxy to generate invoker
                    if (logger.isInfoEnabled()) {
                        logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                    }
                    Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
                    // 此时:根据dubbo-spi机制,会进入  DubboProtocol的export()方法
                    Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
                    exporters.add(exporter);
                }

                MetadataUtils.publishServiceDefinition(url);
            }
        }
        this.urls.add(url);
    }
  1. 获取协议名称,如果为空,设置为dubbo协议。
  2. 设置side为provider。
  3. 设置运行时参数,dubbo版本、时间戳、协议版本等。
  4. 将监控、应用、模块、提供者、协议、服务本身、元数据、接口方法、方法参数等信息放入map中。
  5. 如果是泛化服务,设置泛化服务相关信息。
  6. 根据服务接口找到对应的Wrapper类,拿到Wrapper类中所有的方法名字,放入map。
  7. 获取token配置,放入map,token可以在一定程度上防止人为调用dubbo服务。
  8. 将map中的信息放入服务元数据的attachments中。
  9. 获取host、port并构造URL。
  10. 通过DubboSPI获取ConfiguratorFactory的实现类,该步骤可以可以对URL的内容进行更改或做一些定制化操作。
  11. 根据scope来判断是本地注册还是注册到注册中心,如果是none则不进行导出,如果是local代表导出到本地,仅供本地JVM调用,但是也是会走完整的dubbo流程的。
  12. 将服务的元数据信息放、到元数据中心。
  13. 通过代理工厂生成Invoker,并进行服务导出!!! PROTOCAL.export()。

PROTOCOL.export(wrapperInvoker)

PROTOCOL是一个自适应扩展对象,之前讲过自适应扩展类的方法需要有URL参数或者参数的类里有getURL方法,那么wrapperInvoker里面的URL长什么样呢?由于太长了,省略一部分:
image.png

这个URL的协议头为register, 所以实际调用的方法为RegistryProtocol#export

 public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
        /**
         * 包含了 服务导出 与 服务注册 两个过程
         */

        /**
         * 获取注册中心 URL,以 zookeeper 注册中心为例,得到的示例 URL 如下:
         * zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2
         *         &export=dubbo%3A%2F%2F192.168.1.103%3A20880%2Forg.apache.dubbo.demo.GreetingService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26bind.ip%3D192.168.1.103%26bind.port%3D20880%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26group%3Dgreeting%26interface%3Dorg.apache.dubbo.demo.GreetingService%26mapping-type%3Dmetadata%26mapping.type%3Dmetadata%26metadata-type%3Dremote%26methods%3DhaveNoReturn%2CsetTestgaga%2CgetTestddd%2Chello%26pid%3D2188%26qos.port%3D22222%26release%3D%26revision%3D1.0.0%26side%3Dprovider%26timeout%3D5000%26timestamp%3D1611975477170%26version%3D1.0.0&id=registry1&mapping-type=metadata&mapping.type=metadata&pid=2188&qos.port=22222&timestamp=1611975477162
         * export解码后:
         * export=dubbo://192.168.1.103:20880/org.apache.dubbo.demo.GreetingService?anyhost=true&application=demo-provider&bind.ip=192.168.1.103&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=greeting&interface=org.apache.dubbo.demo.GreetingService&mapping-type=metadata&mapping.type=metadata&metadata-type=remote&methods=haveNoReturn,setTestgaga,getTestddd,hello&pid=2188&qos.port=22222&release=&revision=1.0.0&side=provider&timeout=5000&timestamp=1611975477170&version=1.0.0&id=registry1&mapping-type=metadata&mapping.type=metadata&pid=2188&qos.port=22222&timestamp=1611975477162
         */
        URL registryUrl = getRegistryUrl(originInvoker);
        /**
         * url to export locally 获取已注册的服务提供者 URL,其实就是上面的url的export的值,比如:
         * dubbo://192.168.1.103:20880/org.apache.dubbo.demo.GreetingService?anyhost=true&application=demo-provider&bind.ip=192.168.1.103&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=greeting&interface=org.apache.dubbo.demo.GreetingService&mapping-type=metadata&mapping.type=metadata&metadata-type=remote&methods=haveNoReturn,setTestgaga,getTestddd,hello&pid=2188&qos.port=22222&release=&revision=1.0.0&side=provider&timeout=5000&timestamp=1611975477170&version=1.0.0
         */
        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.
        /**
         * 获取订阅 URL,比如:
         * provider://192.168.1.103:20880/org.apache.dubbo.demo.GreetingService?anyhost=true&application=demo-provider&bind.ip=192.168.1.103&bind.port=20880
         *              &category=configurators&check=false&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=greeting&interface=org.apache.dubbo.demo.GreetingService&mapping-type=metadata&mapping.type=metadata&metadata-type=remote&methods=haveNoReturn,setTestgaga,getTestddd,hello&pid=2188&qos.port=22222&release=&revision=1.0.0&side=provider&timeout=5000&timestamp=1611975477170&version=1.0.0
         */
        final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
        // 创建监听器
        final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
        overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);

        providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
        /**
         * export invoker  导出服务
         */
        final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);

        // url to registry
        // 根据 URL 加载 Registry 实现类,里面有很多spi机制使用,最终得到 :比如 ZookeeperRegistry
        // 但是 外面加了一层wrapper,所以最终 registry =  ListenerRegistryWrapper
        // 同时会注册监听事件!!!!!
        final Registry registry = getRegistry(originInvoker);
        // dubbo://192.168.1.103:20880/org.apache.dubbo.demo.GreetingService?anyhost=true&application=demo-provider&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=greeting&interface=org.apache.dubbo.demo.GreetingService&mapping-type=metadata&mapping.type=metadata&metadata-type=remote&methods=haveNoReturn,setTestgaga,getTestddd,hello&pid=2188&release=&revision=1.0.0&side=provider&timeout=5000&timestamp=1611975477170&version=1.0.0
        final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);

        // decide if we need to delay publish   获取 register 参数
        boolean register = providerUrl.getParameter(REGISTER_KEY, true);
        if (register) {
            // 向注册中心注册服务
            /**
             * 服务注册操作对于 Dubbo 来说不是必需的,通过服务直连的方式就可以绕过注册中心。
             * 但通常我们不会这么做,直连方式不利于服务治理.
             */
            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.
        // 向注册中心进行订阅 override 数据.
        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);

        notifyExport(exporter);
        //Ensure that a new exporter instance is returned every time export
        // 创建并返回 DestroyableExporter
        return new DestroyableExporter<>(exporter);
    }

具体做了这几件事:

  1. 通过registryUrl(zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2) + providerUrl( dubbo://192.168.1.103:20880/org.apache.dubbo.demo.GreetingService?any)生成 overrideSubscribeUrl(provider://192.168.1.103:20880/org.apache.dubbo.demo.GreetingService?anyhost=true&application=demo-provider)在providerUrl的基础上增加参数category=configurators&check=false,根据providerUrl、registryUrl对参数进行简化,因为有些参数是没用的,没有必要放到注册中心,比如:monitor、bind.ip、bind.port等,简化后生成要注册的URL,并注册到注册中心
  2. 进行本地导出:doLocalExport

image.png
由于这时候运行的协议为Dubbo,所以回调用到DubboProtocol,这里会启动Tomcat或者Netty等容器
image.png

  1. 根据url来获取服务key,一般为接口类名
  2. 构造一个DubboExporter,并放入到缓存中
  3. 开启Netty服务:openServer

image.png
image.png

  1. 根据传进来的URL生成服务URL,该URL比原来新增加了channel.readonly.set=TRUE、heartbeat=6000、codec=dubbo参数
  2. 从Url中获取协议的服务器端实现类型,比如:dubbo协议的mina、netty等;http协议的jetty、tomcat等,**默认为netty协议**
  3. 把创建好的Server进行返回,在创建Server的时候,最终**会调用到NettyServer.doOpen**

image.png
设置netty的参数,并启动netty

  1. 根据Invoker 中配置的optimizer 参数获取拓展的自定义序列号处理类

  2. register(registerUrl, registeredProviderUrl)

image.png

  1. 通过Url来获取注册中心实例 ZookeeperRegistry
  2. 调用register进行注册中心注册,最终执行到FailbackRegistry.registry, 这个方法最终调用到zkClient.create将URL注册到注册中心。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/382440.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

指针的学习3

目录 字符指针变量 数组指针变量 二维数组传参的本质 函数指针变量 函数指针变量的创建 函数指针变量的使用 两段有趣的代码 typedef关键字 函数指针数组 转移表 回调函数&#xff1a; 字符指针变量 int main() {char arr[10] "abcdef";char* p1 arr;//…

机器学习2---逻辑回归(基础准备)

逻辑回归是基于线性回归是直线分的也可以做多分类 ## 数学基础 import numpy as np np.pi # 三角函数 np.sin() np.cos() np.tan() # 指数 y3**x # 对数 np.log10(10) np.log2(2) np.e np.log(np.e) #ln(e)# 对数运算 # log(AB) log(A) logB np.log(3*4)np.log(3)np.log(4) #…

vue electron 应用在windows系统上以管理员权限打开应用

打开package.json文件&#xff0c;在build下的win增加配置 "requestedExecutionLevel": "requireAdministrator",

前端JavaScript篇之对闭包的理解

目录 对闭包的理解用途循环中使用闭包解决 var 定义函数的问题 对闭包的理解 闭包是指一个函数能够访问并操作其词法作用域&#xff08;定义时所在的作用域&#xff09;之外的变量的能力。它可以通过在一个函数内部创建另一个函数来实现。内部函数可以访问外部函数的局部变量、…

6.0 Zookeeper session 基本原理详解教程

客户端与服务端之间的连接是基于 TCP 长连接&#xff0c;client 端连接 server 端默认的 2181 端口&#xff0c;也就 是 session 会话。 从第一次连接建立开始&#xff0c;客户端开始会话的生命周期&#xff0c;客户端向服务端的ping包请求&#xff0c;每个会话都可以设置一个…

HTML5+CSS3+移动web——HTML 基础

目录 一、标签语法 HTML的基本框架 1. 标题标签 2. 段落标签 3. 换行和水平线 4. 文本格式化标签 5. 图像标签 6. 路径 相对路径 绝对路径 7. 超链接标签 8. 音频 9. 视频 10. 注释 二、标签结构 一、标签语法 HTML 超文本标记语言——HyperText Markup Langua…

《动手学深度学习(PyTorch版)》笔记8.3

注&#xff1a;书中对代码的讲解并不详细&#xff0c;本文对很多细节做了详细注释。另外&#xff0c;书上的源代码是在Jupyter Notebook上运行的&#xff0c;较为分散&#xff0c;本文将代码集中起来&#xff0c;并加以完善&#xff0c;全部用vscode在python 3.9.18下测试通过&…

计算机网络——07协议层次及服务模型

协议层次及服务模型 协议层次 网络是一个复杂的系统 网络功能复杂&#xff1a;数字信号的物理信号承载、点到点、路由、rdt、进程区分、应用等现实来看&#xff0c;网络的许多构成元素和设备&#xff1a; 主机路由器各种媒体的链路应用协议硬件&#xff0c;软件 问题是&am…

单片机学习路线(简单介绍)

学习单片机对于电子爱好者和未来的嵌入式系统工程师来说是一段激动人心的旅程。单片机因其强大的功能、灵活性以及在各种智能设备中的广泛应用&#xff0c;成为了电子和计算机科学领域一个不可或缺的组成部分。如果你对如何开始这段旅程感到好奇&#xff0c;那么你来对地方了。…

Sqlite3安装步骤

1、Sqlite3以下载文件&#xff0c;配置环境变量的方式进行安装。 2、下方链接为官方的下载地址。 sqlite下载地址 2.1、需要两个下载文件&#xff0c;解压后将他们放在一起&#xff0c;假设解压后的路径为E:\sqlite。 sqlite-dll-win-x64-3450100.zip sqlite-tools-win-x6…

什么是CDR数字音频广播

一、什么是数字音频广播 CDR(China DigilalRadio)&#xff0c;即中国数字音领广播&#xff0c;是运用广播数字化技术&#xff0c;通过对音领信号进行信源编码、信道编码和载波调制传输&#xff0c;来实现数字音频广播业务和数据业务的播出。CDR与传统的FM调频广播相比&#xff…

【蓝桥杯冲冲冲】k 短路 / [SDOI2010] 魔法猪学院

蓝桥杯备赛 | 洛谷做题打卡day33 文章目录 蓝桥杯备赛 | 洛谷做题打卡day33题目背景题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示数据规模数据更新日志 题解代码我的一些话 【模板】k 短路 / [SDOI2010] 魔法猪学院 题目背景 注&#xff1a;对于 k k k 短路问…

第9章 智能租房——详情页

学习目标 掌握详情页房源数据展示功能的逻辑&#xff0c;能够实现在详情页上展示基本信息和配套设施 了解数据可视化&#xff0c;能够说出数据可视化的概念和流程 熟悉ECharts的用法和配置项&#xff0c;能够通过ECharts绘制常用图表&#xff0c;并为图表添加配置项 掌握户型…

2024年 前端JavaScript入门到精通 第一天

主要讲解JavaScript核心知识&#xff0c;包含最新ES6语法&#xff0c;从基础到API再到高级。让你一边学习一边练习&#xff0c;重点知识及时实践&#xff0c;同时每天安排大量作业&#xff0c;加深记忆&#xff0c;巩固学习成果。 1.1 基本软件与准备工作 1.2 JavaScript 案例 …

前端开发_AJAX基本使用

AJAX概念 AJAX是异步的JavaScript和XML(Asynchronous JavaScript And XML)。 简单点说&#xff0c;就是使用XMLHttpRequest对象与服务器通信。 它可以使用JSON&#xff0c;XML&#xff0c;HTML和text文本等格式发送和接收数据。 AJAX最吸引人的就是它的“异步"特性&am…

python-分享篇-GUI界面开发-PyQt5-对QListWidget表格进行数据绑定

代码 # -*- coding: utf-8 -*-# Form implementation generated from reading ui file bindtable.ui # # Created by: PyQt5 UI code generator 5.11.3 # # WARNING! All changes made in this file will be lost! 对QTableWidget表格进行数据绑定from PyQt5 import QtCore, Q…

Wireshark不显示Thrift协议

使用Wireshark对thrift协议进行抓包&#xff0c;但是只显示了传输层的tcp协议&#xff1a; "右键" -> "Decode As" 选择thrift的tcp端口 将“当前”修改为Thrift&#xff0c;然后点击“确定” 设置后&#xff0c;可以发现Wireshark里面显示的协议从Tcp变…

正版软件 - Proxyman:让网络调试变得更智能、更高效

在软件开发的世界里&#xff0c;网络调试一直是开发者和测试工程师的痛点。传统的调试工具往往操作复杂&#xff0c;界面不够直观&#xff0c;而且性能上也难以满足现代应用的需求。今天&#xff0c;我要向大家介绍一款名为Proxyman的网络调试工具&#xff0c;它以其简洁的界面…

第十八篇【传奇开心果短博文系列】Python的OpenCV库技术点案例示例:图像修复和恢复

传奇开心果短博文系列 系列短博文目录Python的OpenCV库技术点案例示例系列短博文目录前言一、常用的图像修复与恢复技术二、插值方法示例代码三、基于纹理合成的方法示例代码四、基于边缘保持的方法示例代码五、基于图像修复模型的方法示例代码六、基于深度学习的方法示例代码七…

Vulnhub靶机:hacksudo-search

一、介绍 运行环境&#xff1a;Virtualbox 攻击机&#xff1a;kali&#xff08;10.0.2.15&#xff09; 靶机&#xff1a;hacksudo-search&#xff08;10.0.2.50&#xff09; 目标&#xff1a;获取靶机root权限和flag 靶机下载地址&#xff1a;https://download.vulnhub.co…