1
头图

前言

大家好,今天开始给大家分享 — Dubbo 专题之 Dubbo 本地调用。在前面的章节中我们介绍了 Dubbo 异步调用,了解了什么是异步调用以及日常的使用场景和实现原理,同时我们知道 Dubbo 的异步调用能够切换我们的请求和业务处理线程,这样充分的利用的 NIO 的机制大大提供了线程的利用率。在这个章节中我们会继续介绍 Dubbo 本地调用。首先我们得了解什么是本地调用?与远程调用的区别以及有什么使用场景和实现原理。那就让我们快速开始吧!

1. Dubbo 本地调用简介

在前面的演示例子中我们使用的都是远程调用,即通过网络通讯调用远程暴露的服务。本地调用就是在同一个 JVM 中进行服务调用不通过网络通讯进行远程调用,在 Dubbo 中本地调用的协议为 injvm://表示。与本地对象上方法调用不同的是,Dubbo 本地调用会经过 Filter 链,其中包括了服务消费端的 Filter 链以及服务提供端的 Filter 链。通过这样的机制,本地消费者和其他消费者都是统一对待,统一监控,服务统一进行治理。下面通过一个示例图演示本地调用和远程调用的区别:

本地调用与远程调用

从上图中可以看出 RPC 调用是通过网络远程调用服务,而我们的本地调用是在同一个 JVM 进程中通过线程调用。这是两者最大的区别,本地调用是不需要到注册中心获取元信息,而远程调用时需要获取元数据的。

Tips:Dubbo 从 2.2.0 每个服务默认都会在本地暴露,无需进行任何配置即可进行本地引用,如果不希望服务进行远程暴露,只需要在providerprotocol设置成 injvm 即可。

2. 多种使用方式

本地调用 Dubbo 提供多种配置方式:

  1. 使用 <dubbo:protocol/> 指定全局默认配置
<dubbo:protocol name="injvm" />
  1. 使用<dubbo:provider/>指定提供者配置
<dubbo:provider protocol="injvm" />
  1. 使用<dubbo:service/>标签指定某个服务配置
<dubbo:service protocol="injvm" />
  1. 通过使用scope="local"配置

同时我们可以通过指定优先使用 injvm进行本地调用,配置方式如下:

  1. 全局指定
<dubbo:consumer injvm="true" /><!--指定所有消费者-->
<dubbo:provider injvm="true" /><!--指定所有服务提供者-->
  1. 指定服务
<dubbo:reference injvm="true" /><!--指定某个服务引用-->
<dubbo:service injvm="true" /><!--指定某个服务暴露-->

3. 使用场景

在前面讨论中我们已经知道本地调用其实是不发起远程调用,而是在同一个中 JVM 中调用。我们在日常工作中其实用得比较少,其中有一种用法和后面将要介绍的Mock调用类似。就是当我们需要调用远程的一个服务时,远程服务并没有开发完成,此时我们就可以使用injvm协议在本地实现一个类似的服务,当调用此服务时调用我们本地的实现服务。当远程服务开发完成时我们切换到远程服务调用,这样就可以实现类似仿真的效果。

4. 示例演示

下面我们以一个获取图书列表实例进行演示。项目结构如下:

idea

这里我们只有一个配置文件dubbo-xml.xml这是因为服务端和消费端都在同一个 JVM 中,其核心配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <dubbo:application name="demo-provider"/>

    <dubbo:protocol name="injvm"/>

    <bean id="target" class="com.muke.dubbocourse.localinvoker.spring.BookFacadeImpl"/>

    <!--暴露服务为Dubbo服务-->
    <dubbo:service interface="com.muke.dubbocourse.common.api.BookFacade" ref="target" />

    <!--引用本地服务-->
    <dubbo:reference id="bookFacade" interface="com.muke.dubbocourse.common.api.BookFacade" scope="local"></dubbo:reference>

</beans>

上面的配置文件中我们可以使用<dubbo:protocol name="injvm"/>方式暴露为本地服务使用scope="local"方式对本地暴露服务进行引用。

5. 实现原理

下面给出一张简化的调用流程图,其中最主要的就是根据我们的配置比如:<dubbo:protocol name="injvm"/>scope="local"来判断是否暴露本地服务或远程服务。

 本地调用

接下来我们通过代码的方式来进行简单的分析:

当我们需要暴露一个服务的时候最终会触发org.apache.dubbo.config.ServiceConfig#export方法调用,核心代码如下:

public synchronized void export() {
     
        //...
        //获取注册中心、检测配置本地存根和本地调用
        checkAndUpdateSubConfigs();

        //...
        //判断是否延迟保留
        if (shouldDelay()) {
            DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
        } else {
            //服务保留
            doExport();
        }

        //...
    }

其中org.apache.dubbo.config.ServiceConfig#checkAndUpdateSubConfigs方法中有如下代码:

 private void checkAndUpdateSubConfigs() {
        //..

        // 判断协议是否配置为 injvm 例如:<dubbo:protocol name="injvm"/>
        if (!isOnlyInJvm()) {
            //获取注册中心配置
            checkRegistry();
        }
        
       //...
    }

此代码中isOnlyInJvm()方法判断协议是否配置为injvm,如果为injvm则不获取注册中心配置。紧接着开始调用下面

org.apache.dubbo.config.ServiceConfig#doExportUrlsFor1Protocol方法,其核心代码如下:

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
        String name = protocolConfig.getName();
              //判断是否配置protocol没有配置模式使用 dubbo 协议
        if (StringUtils.isEmpty(name)) {
            name = DUBBO;
        }

                //...
                
              //获取配置的 scope 例如:scope="local"
        String scope = url.getParameter(SCOPE_KEY);
        // scope 不等于 none 
        if (!SCOPE_NONE.equalsIgnoreCase(scope)) {

            // scope 不等于 remote 暴露本地服务
            if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
                exportLocal(url);
            }
             // scope 不等于 local 暴露远程服务
            if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
                if (CollectionUtils.isNotEmpty(registryURLs)) {
                    for (URL registryURL : registryURLs) {
                        //判断如果是injvm协议不暴露远程服务
                        if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
                            continue;
                        }
                       
                                                //创建远程调用代理对象
                        Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
                        DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                        Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
                        exporters.add(exporter);
                    }
                } else {
                    
                  //...
                }
                //...
            }
        }
        this.urls.add(url);
    }

上面的代码主要逻辑就是判断是否需要本地暴露和远程暴露,如果远程暴露则需要到注册中心注册服务元数据信息。

6. 小结

在本小节中我们主要学习了 Dubbo 中的本地调用,同时我们也介绍了常见的配置方式和一些使用场景。结合到我们简化的流程图和源码的讲解分析我们大致可归纳为两大配置类:一类是使用injvm协议进行配置、一类是使用scope来配置。

本节课程的重点如下:

  1. 理解 Dubbo 中本地调用
  2. 了解常见使用方式
  3. 了解本地调用使用场景
  4. 了解本地调用实现原理

作者

个人从事金融行业,就职过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就职于某银行负责统一支付系统建设。自身对金融行业有强烈的爱好。同时也实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域。同时也热衷于技术分享创立公众号和博客站点对知识体系进行分享。关注公众号:青年IT男 获取最新技术文章推送!

博客地址: http://youngitman.tech

微信公众号:


青年IT男
22 声望9 粉丝

« 上一篇
Dubbo 泛化引用
下一篇 »
Dubbo Stub与Mock