Dubbo基础(四)- Provider的初始化过程C:服务暴露详解

前两篇详细讲解了 服务初始化过程的一些细节,分别是:

  1. 外部化配置初始化过程
  2. 配置初始化顺序
  3. 多个配置,先后优先级问题

本篇文章则着重分析服务服务暴露,
可以以以下几个问题进行:

  1. 在服务暴露中,是否有哪些dubbo功能特性的预制参数会加入到url中呢?

以上问题看完文章后相信大家就可以清楚,若有疑问,关注博主公众号:六点A君,回复标题获取最新答案><

doExport

在检查完配置之后,就会进入 doExport,当然会判定是否需要延迟加载等情况:

    public synchronized void export() {
        checkAndUpdateSubConfigs();

        if (!shouldExport()) {
            return;
        }
		// 延迟加载
        if (shouldDelay()) {
            delayExportExecutor.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
        } else {
            doExport();
        }
    }

doExport中 ,会判断以下,如果已经暴露过了,那么就不需要再一次暴露,通过 一个 transient volatile boolean 的变量进行判定。

而后调用 doExportUrls

doExportUrls

    private void doExportUrls() {
        List<URL> registryURLs = loadRegistries(true);
        for (ProtocolConfig protocolConfig : protocols) {
        // 拼凑 pathKey
            String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
            // 构建 ProviderModel
            ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
            // 将已发布服务存起来
            ApplicationModel.initProviderModel(pathKey, providerModel);
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

首先会执行 loadRegistries,当是是服务端时候,传true,而消费者则是传false,这个方法,主要就是遍历,当地址不是N/A时候。

  1. 遍历 所有的 RegistryConfig,将所有的 注册连接 address 拿到。
  2. application、config、path、protocol 等参数封装为map并加载到url上面
  3. 通过正则分割address 得到多个url,将url和上面参数拼接

最后并返回。

URL.buildKey 中,将从 protocolConfig中获取的path,group,version进行拼装,最终形式类似于 ${group}/${path}:${version}
而当groupversion为空是,则直接不会有默认值。path 则不回为空,默认为 全类名。

ProviderModel 则是代表一个已发布的服务。里面会将 interfaceClass 中所有方法 遍历并暂存起来到内部 methods 中,而方法则使用 ProviderMethodModel 表示。

在 ApplicationModel中维护这一个 ConcurrentMap<String, ProviderModel> PROVIDED_SERVICES ,用于存储已经暴露的服务,而 ApplicationModel.initProviderModel 则主要做这件事,将 键值对为 pathKey, providerModel 存到里面。

doExportUrlsFor1Protocol 准备阶段

在做完了前期全部准备工作之后,就要开始主体暴露服务了。
由于涉及配置优先级顺序,dubbo还是执行了一大波 appendParameters 来获取各个不同配置:

        String name = protocolConfig.getName();
        if (StringUtils.isEmpty(name)) {
            name = DUBBO;
        }

        Map<String, String> map = new HashMap<String, String>();
        map.put(SIDE_KEY, PROVIDER_SIDE);
		// 加载多个配置项
        appendRuntimeParameters(map);
        appendParameters(map, metrics);
        appendParameters(map, application);
        appendParameters(map, module);
        // remove 'default.' prefix for configs from ProviderConfig
        // appendParameters(map, provider, Constants.DEFAULT_KEY);
        appendParameters(map, provider);
        appendParameters(map, protocolConfig);
        appendParameters(map, this);

如上,如果是单配置,则配置优先级是从下往上:
this > protocolConfig > provider > module > application > metrics
而如果支持多配置,即 @Parameterappend=true 则会以逗号分割多个配置值。

接下来看对需要暴露的接口方法的处理:

        if (CollectionUtils.isNotEmpty(methods)) {
            for (MethodConfig method : methods) {
                appendParameters(map, method, method.getName());
                String retryKey = method.getName() + ".retry";
                // 设置 retries 的值。
                if (map.containsKey(retryKey)) {
                    String retryValue = map.remove(retryKey);
                    if ("false".equals(retryValue)) {
                        map.put(method.getName() + ".retries", "0");
                    }
                }
                List<ArgumentConfig> arguments = method.getArguments();
                if (CollectionUtils.isNotEmpty(arguments)) {
                    for (ArgumentConfig argument : arguments) {
                        // convert argument type
                        // 说明有参数
                        if (argument.getType() != null && argument.getType().length() > 0) {
                            Method[] methods = interfaceClass.getMethods();
                            // 使用反射,获取所有interface的所有方法
                            if (methods != null && methods.length > 0) {
                                for (int i = 0; i < methods.length; i++) {
                                    String methodName = methods[i].getName();
                                    // 找到了,就是这个方法,它配置了dubbo 的arguments,从而判断是一个callback,还是多个callback
                                    if (methodName.equals(method.getName())) {
                                        Class<?>[] argtypes = methods[i].getParameterTypes();
                                        // 1个callback
                                        if (argument.getIndex() != -1) {
                                            if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
                                                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 {
                                        	// 多个callback
                                            for (int j = 0; j < argtypes.length; j++) {
                                                Class<?> argclazz = argtypes[j];
                                                if (argclazz.getName().equals(argument.getType())) {
                                                    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) {
                            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
        }

上文代码中,主要是增加对callback 的支持。

  1. 首先会对配置而来的 List<MethodConfig> 进行检测,看是否配置了方法级别的参数
  2. 获取 interface 里面方法集合,找出配置了的 argument参数的方法
  3. 如果有,则会进行初始化 callback 参数

过完上面的 callback参数解析之后,会判断是否为 generic,如果不是 generic 泛化调用,会加入增加诸如 reversion

		// 判断是否为 generic 泛化调用
        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);
            }
			// 通过 Dubbo字节码生成器获取所有的 方法
            String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
            if (methods.length == 0) {
                logger.warn("No method found in service interface " + interfaceClass.getName());
                map.put(METHODS_KEY, ANY_VALUE);
            } else {
            // 将方法以逗号分割形式加入到map中。
                map.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
            }
        }

以上代码判断了是否为泛化调用,以及加入了版本号, 所有方法参数等。
如果有token,则会将token也加入到map中。

doExportUrlsFor1Protocol 开始export
  		// 获取配置的ip,并连接绑定端口
 		String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
        Integer port = this.findConfigedPorts(protocolConfig, name, map);
        // 将ip端口,以及map,以及 path 拼凑成url
        URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
		// 获取 动态配置中心,并将url写道 配置中心
        if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                .hasExtension(url.getProtocol())) {
            url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                    .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
        }

后面还会检查 SCOPE_KEY,从而判断是否应该export。
后续仍然会加入 dynamicmonitor 等信息。

而后通过 ProxyFactory 生成代理类,并由包装类 DelegateProviderMetaDataInvoker 再执行 export 方法:

                        Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
                        DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                        Exporter<?> exporter = protocol.export(wrapperInvoker);
                        exporters.add(exporter);

ServiceConfig 的 代理工厂使用的是默认的代理工厂:

private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

JavassistProxyFactory 代理类。

最终通过 ref、interface、url 等信息,生成了 Invoker 返回。
而由 DelegateProviderMetaDataInvoker 包装了一层之后,调用 protocol.export 进行暴露。默认是 DubboProtocolexport 进行调用。

DubboProtocol 的 export

export 方法里面,首先判断了对 stub 特性支持,stub 即本地存根,http://dubbo.apache.org/en-us/docs/user/demos/local-stub.html

    @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        URL url = invoker.getUrl();

        // 构造一个Exporter
        String key = serviceKey(url);
        DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
        exporterMap.put(key, exporter);

        // 增加对Exporter支持
        Boolean isStubSupportEvent = url.getParameter(STUB_EVENT_KEY, DEFAULT_STUB_EVENT);
        Boolean isCallbackservice = url.getParameter(IS_CALLBACK_SERVICE, false);
        if (isStubSupportEvent && !isCallbackservice) {
            String stubServiceMethods = url.getParameter(STUB_EVENT_METHODS_KEY);
            if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
                if (logger.isWarnEnabled()) {
                    logger.warn(new IllegalStateException("consumer [" + url.getParameter(INTERFACE_KEY) +
                            "], has set stubproxy support event ,but no stub methods founded."));
                }

            } else {
                stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
            }
        }
		
        openServer(url);
        optimizeSerialization(url);

        return exporter;
    }

而后会调用 openServer ,其主要作用是开启一个 ExchangeServer, 实际就是对 底层网络通信的封装,在dubbo 传输协议中,使用的是Netty 作为网络传输协议,Dubbo 内置也支持 Grizzly、Mina 等传输协议。

optimizeSerialization 则是 序列化优化器来优化url,不过目前版本(2.7.2)并没有实现其相关逻辑, SerializationOptimizer 没有子类。

最后返回该 exporter

关于 ExchangeServer, 将会以专门章节分析。

总结

服务端服务暴露中,主要做了以下几件事:

  1. 加载 相关 配置
  2. 设置dubbo配置中一些特性,如callback, 并判断是否为 generic 泛化接口
  3. 将 dubbo 方法及配置调用封装成 URL
  4. 如果有配置中心 ConfiguratorFactory,则将url信息配置到 配置中心,具体需要看 AbstractConfigurator 继承者逻辑,默认的 dubbo 则不需要配置
  5. 启动 网络传输层 服务,即 ExchangeServer

下一章 则进入 Dubbo 消费者服务分析,当然服务消费者和服务提供者会有很多类似的逻辑,这点看 dubbo 的 config 代码复用性就可以略知一二。

觉得博主写的有用,不妨关注博主公众号: 六点A君。
哈哈哈,Dubbo小吃街不迷路:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值