专栏首页ytaoDubbo之服务暴露

Dubbo之服务暴露

前言

本文 Dubbo 使用版本 2.7.5

Dubbo 通过使用 dubbo:service配置或 @service在解析完配置后进行服务暴露,供服务消费者消费。

Dubbo 的服务暴露有两种:

  • 远程暴露
  • 本地暴露

可以通过 scope显式指定暴露方式:

  • none 不暴露
  • remote 远程暴露
  • local 本地暴露

服务暴露流程

下面是一个服务暴露的流程图:

ProxyFactory 是动态代理,用来创建 Invoker 对象,实现代理使用 JavassistProxyFactoryJdkProxyFactory

Invoker 是一个服务对象实例,Dubbo 框架的实体域。它可以是一个本地的实现,一个远程的实现或一个集群的实现,可以向它发起 Invoker 调用。

Protocol 是服务域,负责 Invoker 的生命周期管理,是 Invoker 暴露和引用的主要功能入口,对应该类的 exportrefer方法。

Exporter 是根据不同协议暴露 Invoker 进行封装的类,它会根据不同的协议头进行识别(比如:registry://dubbo://),调用对应 XXXProtocolexport()方法。

从上图中可以看到,Dubbo 中服务暴露分为两个大步骤:第一步通过代理将服务实例转换成 Invoker,这就是通过我们常用的反射实现。第二步将 Invoker 根据具体的协议转换成 Exporter,这是就是我们要分析的核心。从这里可以看到 Dubbo 服务对象都是围绕 Invoker 进行工作。

远程暴露

服务远程暴露从字面上理解,就是将服务跨网络进行远程通信,并非同一 JVM 中的服务进行调用。服务最后都是转换成 org.apache.dubbo.config.spring.ServiceBean,它的UML类图:

ServiceBean继承自 ServiceConfig,服务在 ServiceConfig#doExportUrls根据不同协议进行暴露。

通过获取所有注册中心实例(registryURLs)后,进行依次暴露,暴露操作在 doExportUrlsFor1Protocol中。

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
    Map<String, String> map = new HashMap<String, String>();
    // 配置信息存入 map

    .....

    // 获取服务URL
    String host = findConfigedHosts(protocolConfig, registryURLs, map);
    Integer port = findConfigedPorts(protocolConfig, name, map);
    URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);

    .....

    String scope = url.getParameter(SCOPE_KEY);
    // 如果 scope 配置为 none,则服务不进行暴露
    if (!SCOPE_NONE.equalsIgnoreCase(scope)) {

        // 本地暴露
        if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
            exportLocal(url);
        }
        // 远程暴露
        if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
            // 判断是否有注册中心
            if (CollectionUtils.isNotEmpty(registryURLs)) {
                for (URL registryURL : registryURLs) {
                    //if protocol is only injvm ,not register
                    if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
                        continue;
                    }
                    url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
                    // 获取监控URL
                    URL monitorUrl = ConfigValidationUtils.loadMonitor(this, registryURL);
                    if (monitorUrl != null) {
                        // 追加监控上报地址,在拦截器上报数据
                        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);
                        }
                    }

                    // For providers, this is used to enable custom proxy to generate invoker
                    String proxy = url.getParameter(PROXY_KEY);
                    if (StringUtils.isNotEmpty(proxy)) {
                        registryURL = registryURL.addParameter(PROXY_KEY, proxy);
                    }

                    // 将服务对象转换成 Invoker
                    Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                    // 暴露服务,向注册中心注册服务,进入对应的 RegistryProtocol
                    Exporter<?> exporter = protocol.export(wrapperInvoker);
                    exporters.add(exporter);
                }
            } else {    // 没有注册中心时
                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);
                // 直接暴露服务
                Exporter<?> exporter = protocol.export(wrapperInvoker);
                exporters.add(exporter);
            }
            /**
             * 存储Dubbo服务的元数据,元数据可以存储在远端配置中心和本地,默认是存储在本地
             * @since 2.7.0
             * ServiceData Store
             */
            WritableMetadataService metadataService = WritableMetadataService.getExtension(url.getParameter(METADATA_KEY, DEFAULT_METADATA_STORAGE_TYPE));
            if (metadataService != null) {
                metadataService.publishServiceDefinition(url);
            }

        }
    }
    this.urls.add(url);
}

上面是代码片段为暴露服务的核心,可以看到 scope 由三个值控制是否暴露和远程或本地暴露,默认远程和本地都暴露。在远程调用中,分为使用注册中心暴露直接暴露(默认dubbo协议),它们之间的区别在url上:

  • 无注册中心:dubbo://192.168.3.19:20880/xxxx
  • 有注册中心:registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=provider&dubbo=2.0.2&export=dubbo://192.168.3.19:20880/xxxx

无注册中心的直接暴露服务。有注册中心的先创建注册中心,再得到 export 的服务地址,然后暴露服务,当服务暴露成功后把服务元数据注册到注册中心。

代码中 protocol#export会根据服务 url 的请求头进行区分不同 XXXProtocol#export的逻辑,比如。目前 Dubbo 中有以下几种:

本地暴露

同一个应用中,可能既要提供服务远程暴露给其他应用引用,也要给自身提供引用。如果只提供远程暴露的话,当自身应用需要引用自身的服务时,需要通过远程通信访问,那么这大大浪费网络资源。这是就需要用 injvm 协议暴露,就是我们所说的本地暴露,无需跨网络远程通信,可以更好的节省资源。通过上面代码中,我们知道本地暴露调用的是 ServiceConfig#exportLocal方法。

本地暴露会指定 injvm 协议,并且 host 指定为本地 127.0.0.1和端口号为0。protocol.export 调用 InjvmProtocol#export 实现:

@Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
    }

export 中返回了 InjvmExporter 实例化对象。

class InjvmExporter<T> extends AbstractExporter<T> {

    private final String key;

    private final Map<String, Exporter<?>> exporterMap;

    InjvmExporter(Invoker<T> invoker, String key, Map<String, Exporter<?>> exporterMap) {
        super(invoker);
        this.key = key;
        this.exporterMap = exporterMap;
        exporterMap.put(key, this);
    }

    @Override
    public void unexport() {
        super.unexport();
        exporterMap.remove(key);
    }

}

本地暴露就比较简单,将 Invoker 直接保存在 InjvmExporter 的 exporterMap 中。

最后

本篇对 Dubbo 的服务暴露流程进行了分析,核心点就是开篇图中的得到 Invoker 后转化到 Export。其中更多详细的地方,由于展开后篇幅太大,不能一一写到,会在今后相关的 Dubbo 文章再进行讲解。

关注【ytao】,更多原创好文

本文分享自微信公众号 - ytao(ytao-blog),作者:ytao

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-03-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Dubbo之服务消费原理

    上篇文章《Dubbo之服务暴露》分析 Dubbo 服务是如何暴露的,本文接着分析 Dubbo 服务的消费流程。主要从以下几个方面进行分析:注册中心的暴露;通过注...

    ytao
  • Dubbo 负载均衡的实现

    负载均衡是指在集群中,将多个数据请求分散在不同单元上进行执行,主要为了提高系统容错能力和加强系统对数据的处理能力。

    ytao
  • 初探Tomcat的架构设计

    本文将基于 Tomcat8 进行分析,具体版本为 Tomcat8 当前官网最新修改(2019-11-21 09:28)的版本 v8.5.49

    ytao
  • PyTorch 的这些更新,你都知道吗?

    昱良
  • PyTorch 重磅更新,不只是支持 Windows

    这次版本的主要更新一些性能的优化,包括权衡内存计算,提供 Windows 支持,24个基础分布,变量及数据类型,零维张量,张量变量合并,支持 CuDNN 7.1...

    AI科技大本营
  • JPEG-XS:用于IP视频的母片图像(mezzanine image)编解码器

    本帖参考T. Richter等人发表在SMPTE Motion Imaging Journal的文章JPEG-XS—A High-Quality Mezzani...

    用户1324186
  • iOS基础开发实践:iMessage Extension浅析

    天天P图攻城狮
  • Python|神经网络框架torch包[1]

    torch包主要是用于多维张量的数据结构和用于张量的数学操作。除此之外,还提供了许多用于张量有效序列化和任意类型的工具,还有一些其他相关的工具。

    Rare0716
  • Dubbo 源码分析 - 服务引用

    在上一篇文章中,我详细的分析了服务导出的原理。本篇文章我们趁热打铁,继续分析服务引用的原理。在 Dubbo 中,我们可以通过两种方式引用远程服务。第一种是使用服...

    田小波
  • 编写高效的PyTorch代码技巧(上)

    原文:https://github.com/vahidk/EffectivePyTorch

    材ccc

扫码关注云+社区

领取腾讯云代金券