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

news2024/11/18 4:41:21

入口

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注册到注册中心。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【北邮鲁鹏老师计算机视觉课程笔记】03 edge 边缘检测

【北邮鲁鹏老师计算机视觉课程笔记】03 1 边缘检测 有几种边缘&#xff1f; ①实体上的边缘 ②深度上的边缘 ③符号的边缘 ④阴影产生的边缘 不同任务关注的边缘不一样 2 边缘的性质 边缘在信号突变的地方 在数学上如何寻找信号突变的地方&#xff1f;导数 用近似的方法 可以…

【DDD】学习笔记-领域模型与函数范式

函数范式 REA 的 Ken Scambler 认为函数范式的主要特征为&#xff1a;模块化&#xff08;Modularity&#xff09;、抽象化&#xff08;Abstraction&#xff09;和可组合&#xff08;Composability&#xff09;&#xff0c;这三个特征可以帮助我们编写简单的程序。 通常&#…

电商网站基础布局——以小兔鲜为例

项目准备 /* base.css */ /* 內减模式 */ * {margin: 0;padding: 0;box-sizing: border-box; }/* 设置网页统一的字体大小、行高、字体系列相关属性 */ body {font: 16px/1.5 "Helvetica Neue", Helvetica, Arial, "Microsoft Yahei","Hiragino Sans…

编码安全风险是什么,如何进行有效的防护

2011年6月28日晚20时左右&#xff0c;新浪微博突然爆发XSS&#xff0c;大批用户中招&#xff0c;被XSS攻击的用户点击恶意链接后并自动关注一位名为HELLOSAMY的用户&#xff0c;之后开始自动转发微博和私信好友来继续传播恶意地址。不少认证用户中招&#xff0c;也导致该XSS被更…

【深蓝学院】移动机器人运动规划--第4章 动力学约束下的运动规划--笔记

0. Outline 1. Introduction 什么是kinodynamic&#xff1f; 运动学&#xff08;Kinematics&#xff09;和动力学&#xff08;Dynamics&#xff09;都是力学的分支&#xff0c;涉及物体的运动&#xff0c;但它们研究的焦点不同。 运动学专注于描述物体的运动&#xff0c;而…

反应式编程

反应式编程 前言1 反应式编程概览2 初识 Reactor2.1 绘制反应式流图2.2 添加 Reactor 依赖 3.使用常见的反应式操作3.1 创建反应式类型3.2 组合反应式类型3.3 转换和过滤反应式流3.4 在反应式类型上执行逻辑操作 总结 前言 你有过订阅报纸或者杂志的经历吗?互联网的确从传统的…

第66讲管理员登录功能实现

项目样式初始化 放assets目录下&#xff1b; border.css charset "utf-8"; .border, .border-top, .border-right, .border-bottom, .border-left, .border-topbottom, .border-rightleft, .border-topleft, .border-rightbottom, .border-topright, .border-botto…

WWW 万维网

万维网概述 万维网 WWW (World Wide Web) 并非某种特殊的计算机网络。 万维网是一个大规模的、联机式的信息储藏所。 万维网用链接的方法能非常方便地从互联网上的一个站点访问另一个站点&#xff0c;从而主动地按需获取丰富的信息。 这种访问方式称为“链接”。 万维网是分…

线上编程答疑解惑回顾,初学编程中文编程在线屏幕共享演示

线上编程答疑解惑回顾&#xff0c;初学编程中文编程在线屏幕共享演示 一、学编程过程中有不懂的怎么办&#xff1f; 编程入门视频教程链接 https://edu.csdn.net/course/detail/39036 编程工具及实例源码文件下载可以点击最下方官网卡片——软件下载——常用工具下载——编…

Python入门知识点分享——(二十)继承和方法重写

今天是大年三十&#xff0c;祝大家龙年大吉&#xff0c;当然无论何时何地&#xff0c;我们都不要忘记继续学习。今天介绍的是继承和方法重写这两种面向对象编程特点。继承机制指的是&#xff0c;一个类&#xff08;我们称其为子类或派生类&#xff09;可以使用另一个类&#xf…

无心剑中译佚名《春回大地》

The Coming of Spring 春回大地 I am coming, little maiden, With the pleasant sunshine laden, With the honey for the bee, With the blossom for the tree. 我来啦&#xff0c;小姑娘 满载着欣悦的阳光 蜂儿有蜜酿 树儿有花绽放 Every little stream is bright, All …

【Leetcode】LCP 30. 魔塔游戏

文章目录 题目思路代码结果 题目 题目链接 小扣当前位于魔塔游戏第一层&#xff0c;共有 N 个房间&#xff0c;编号为 0 ~ N-1。每个房间的补血道具/怪物对于血量影响记于数组 nums&#xff0c;其中正数表示道具补血数值&#xff0c;即血量增加对应数值&#xff1b;负数表示怪…

Apache Zeppelin 整合 Spark 和 Hudi

一 环境信息 1.1 组件版本 组件版本Spark3.2.3Hudi0.14.0Zeppelin0.11.0-SNAPSHOT 1.2 环境准备 Zeppelin 整合 Spark 参考&#xff1a;Apache Zeppelin 一文打尽Hudi0.14.0编译参考&#xff1a;Hudi0.14.0 最新编译 二 整合 Spark 和 Hudi 2.1 配置 %spark.confSPARK_H…

moduleID的使用

整个平台上有很多相同的功能&#xff0c;但是需要不同的内容。例如各个模块自己的首页上有滚动新闻、有友好链接等等。为了公用这些功能&#xff0c;平台引入了moduleID的解决方案。 在前端的配置文件中&#xff0c;配置了模块号&#xff1a; 前端页面请求滚动新闻时&#xff0…

Sam Altman计划筹集5至7万亿美元;OPPO发布AI时代新功能

&#x1f989; AI新闻 &#x1f680; Sam Altman计划筹集5至7万亿美元&#xff0c;建立全球芯片帝国 摘要&#xff1a;Sam Altman宣布计划筹集5至7万亿美元来建立全球芯片帝国&#xff0c;以满足日益增长的AI基础设施需求。他已在全球寻求资金&#xff0c;包括中东土豪。此外…

Flume拦截器使用-实现分表、解决零点漂移等

1.场景分析 使用flume做数据传输时&#xff0c;可能遇到将一个数据流中的多张表分别保存到各自位置的问题&#xff0c;同时由于采集时间和数据实际发生时间存在差异&#xff0c;因此需要根据数据实际发生时间进行分区保存。 鉴于此&#xff0c;需要设计flume拦截器配置conf文件…

Java 内存区域介绍

&#xff08;1&#xff09;程序计数器 程序计数器主要有两个作用&#xff1a; 字节码解释器通过改变程序计数器来依次读取指令&#xff0c;从而实现代码的流程控制&#xff0c;如&#xff1a;顺序执行、选择、循环、异常处理。 在多线程的情况下&#xff0c;程序计数器用于记录…

【开源】JAVA+Vue.js实现计算机机房作业管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 登录注册模块2.2 课程管理模块2.3 课时管理模块2.4 学生作业模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 课程表3.2.2 课时表3.2.3 学生作业表 四、系统展示五、核心代码5.1 查询课程数据5.2 新增课时5.3 提交作…

360 安全浏览器 - 页面布局 - 常用网址

360 安全浏览器 - 页面布局 - 常用网址 自定义样式 let myStyle {https://www.baidu.com/: {color: #001483,backgroundColor: #FFF,icon: https://www.baidu.com/favicon.ico},https://blog.csdn.net/jx520: {backgroundColor: #fc5531,icon: https://g.csdnimg.cn/static/l…

离线数仓(一)【数仓概念、需求架构】

前言 今天开始学习数仓的内容&#xff0c;之前花费一年半的时间已经学完了 Hadoop、Hive、Zookeeper、Spark、HBase、Flume、Sqoop、Kafka、Flink 等基础组件。把学过的内容用到实践这是最重要的&#xff0c;相信会有很大的收获。 1、数据仓库概念 1.1、概念 数据仓库&#x…