【dubbo源码解析】--- dubbo的服务暴露+服务消费(RPC调用)底层原理深入探析

本文对应源码地址:https://github.com/nieandsun/dubbo-study


文章目录

  • 1 开篇
  • 2 dubbo服务暴露过程底层原理探秘
    • 2.1 spring环境下dubbo服务暴露过程的前置知识
      • 2.1.1【spring解析要暴露服务的bean ---> 进行服务暴露】 整体过程概览
      • 2.1.2 ProxyFactory和Protocol接口简介
    • 2.2 实现一个自己的dubbo服务端
    • 2.3 dubbo服务暴露过程主要流程梳理
  • 3 dubbo服务消费过程底层原理探秘
    • 3.1 spring环境下dubbo服务消费过程的前置知识
    • 3.2 实现一个自己的dubbo消费端
    • 3.3 dubbo消费端调用服务的主要流程梳理
  • 4 简单测试我们自己写的服务端和消费端
  • 5 我心中Dubbo里最核心的概念: SPI/Invoker


1 开篇

想要在一个JVM里调用另一个JVM里的的方法,或许你会想到如下的姿势:

  • HttpClient
  • RestTemplate
  • WebService
  • ServerSocket/Socket
  • RMI — 可以参看我的上篇文章《【dubbo源码解析~番外篇】— JDK和Spring的RMI使用姿势简介》

但是在真正进行方法调用时,你不仅要考虑传什么参数、调用哪个方法,还得考虑对方服务的暴露地址等。
而用过dubbo的肯定都知道,它可以做到:在一个JVM(消费端)里调用另一个JVM(服务端)的方法,就如同你在另一个JVM(服务端)直接调自己的方法一样 —> 这究竟是如何做到的呢,本篇文章将主要来探索一下这个问题。


2 dubbo服务暴露过程底层原理探秘


2.1 spring环境下dubbo服务暴露过程的前置知识


2.1.1【spring解析要暴露服务的bean —> 进行服务暴露】 整体过程概览

无论是使用注解,还是xml配置的方式,大家必须要有的前置知识是,dubbo中一个要向外暴露的服务(service),会先被spring解析并包装为一个ServiceBean,然后再拿着该ServiceBean进行真正的服务暴露。在dubbo中真正进行服务暴露的源码如下:
【dubbo源码解析】--- dubbo的服务暴露+服务消费(RPC调用)底层原理深入探析_第1张图片
其实在该段代码中还涉及到了几个重要的概念:

  • (1)Invoker

  • (2)ProxyFactory — 代理工厂,主要有两个作用:
    • ① 生成Invoker
      • 注意1:在provider端生成的Invoker(实际为AbstractProxyInvoker)里包含了【具体要暴露的服务(即ref)】【服务的接口】【要暴露服务的url】
      • 注意2:在consumer端生成的Invoker(实际为AbstractInvoker)里至少包含了【服务的接口】【要暴露服务的url】
    • ②生成服务的代理类
      • 如果代理工厂选择的为JavassistProxyFactory,则为服务生成静态代理类
      • 如果代理工厂选择的为JdkProxyFactory,则为服务生成动态代理类

  • (3)Protocol — 协议,也主要有两个作用:
    • ① 拿着Invoker中的【url】 、【服务的接口】、和【根据该Invoker 用ProxyFactory生成的代理类】进行服务暴露
    • ② 接收到消费者的请求后,直接将请求参数信息交给代理类 —> 然后代理类会将请求信息转给(2)中生成的Invoker,并在Invoker里调用具体的服务 —> 这里后面会继续深化!!!

2.1.2 ProxyFactory和Protocol接口简介

同时还需要注意 ProxyFactoryProtocol 都是dubbo的SPI扩展点, 在dubbo源码中获取PROTOCOL和PROXY_FACTORY的姿势如下:
【dubbo源码解析】--- dubbo的服务暴露+服务消费(RPC调用)底层原理深入探析_第2张图片
看过我《【dubbo源码解析】 — dubbo spi 机制(@SPI、@Adaptive)详解》这篇文章的你,肯定会想翻一下 ProxyFactoryProtocol 的源码,这里我们简单看一下:

  • Protocol接口的源码
@SPI("dubbo") //dubbo默认使用的协议为dubbo协议
public interface Protocol {

    int getDefaultPort();

    @Adaptive //在方法上标注该注解,说明会静态代理Protocol 实现类的该方法 ---> 服务暴露的方法
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;

    @Adaptive//在方法上标注该注解,说明会静态代理Protocol 实现类的该方法 ---> 根据class类型和URL获取Invoker【消费端的逻辑】
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
   
    void destroy();
   
    default List<ProtocolServer> getServers() {
        return Collections.emptyList();
    }
}
  • ProxyFactory接口的源码
@SPI("javassist")//dubbo默认使用的代理工厂为静态代理工厂
public interface ProxyFactory {
	//PROXY_KEY其实指代的就是字符串“proxy”
    @Adaptive({PROXY_KEY}) //在方法上标注该注解,说明会静态代理ProxyFactory 实现类的该方法 ---> 获取要暴露的服务的代理类
    <T> T getProxy(Invoker<T> invoker) throws RpcException;

    @Adaptive({PROXY_KEY})//在方法上标注该注解,说明会静态代理ProxyFactory 实现类的该方法 ---> 获取服务的代理类
    <T> T getProxy(Invoker<T> invoker, boolean generic) throws RpcException;

    @Adaptive({PROXY_KEY})//在方法上标注该注解,说明会静态代理ProxyFactory 实现类的该方法 ---> 其实这里是生成Invoker
    <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException;

通过上面的源码分析,你会想到什么呢??? ---》 这里请大家自己先开脑洞。。。


2.2 实现一个自己的dubbo服务端

有了上面的知识后,其实我们就可以仿照dubbo的源码,自己实现一个服务端了。当然本文仅仅是探索RPC远程调用的原理,所以这里的Serive,我们大可以直接new一个出来。

仿照dubbo源码自己写的dubbo服务端:

ExtensionLoader<ProxyFactory> proxyLoader = ExtensionLoader.getExtensionLoader(ProxyFactory.class);
//支持的协议:dubbo、http、hessian、rmi等
ExtensionLoader<Protocol> protocolLoader = ExtensionLoader.getExtensionLoader(Protocol.class);

//URL protocol_url = URL.valueOf("dubbo://127.0.0.1:9300/" + DemoService.class.getName());//静态代理
URL protocol_url = URL.valueOf("dubbo://127.0.0.1:9300/" + DemoService.class.getName() + "?proxy=jdk"); //动态代理


@Test
public void serverRpc() throws IOException {
    DemoService service = new DemoServiceImpl();
    //生成代理工厂
    //  --- 由URL确定到底是动态代理工厂(JdkProxyFactory)还是静态代理工厂(JavassistProxyFactory)
    //  --- 默认情况下为静态代理工厂
    ProxyFactory proxy = proxyLoader.getAdaptiveExtension();
    //由代理工厂生成Invoker对象
    //  --- Invoker对象里包含了具体的服务(service,在dubbo源码里服务端称为ref)、服务的接口和要暴露服务的url
    //  --- 但值得注意的是这里返回的Invoker对象,是用Invoker接口进行接收的,也就是说通过下面的serviceInvoker,只能获取到service的接口
    Invoker<DemoService> serviceInvoker = proxy.getInvoker(service, DemoService.class, protocol_url);

    //获取具体的协议
    //  ---由URL确定到底使用哪个协议,默认情况下使用dubbo协议
    Protocol protocol = protocolLoader.getAdaptiveExtension();
    
    //利用protocol暴露服务
    //  --- 暴露服务的具体流程为
    //      --- (1) 拿到服务的【动态代理类或者静态代理类】、【接口】、以及【url】
    //      --- (2) 拿着(1)中获取到的三个内容进行真正的服务暴露
    Exporter<DemoService> exporter = protocol.export(serviceInvoker);
    System.out.println("server 启动协议:" + protocol_url.getProtocol());
    // 保证服务一直开着
    System.in.read();
    exporter.unexport();
}

这里简单提一下dubbo的jar包的引入问题。
如果你想了解dubbo具体的协议,可以单独引入各个协议的jar包,比如说上面的代码如果想支持dubbo、http、hessian、rmi四种协议,需要引入的jar包如下:

	
	<dependency>
	    <groupId>com.alibabagroupId>
	    <artifactId>dubbo-rpc-dubboartifactId>
	    <version>${dubbo.version}version>
	dependency>	
	
	
	<dependency>
	    <groupId>com.alibabagroupId>
	    <artifactId>dubbo-remoting-netty4artifactId>
	    <version>${dubbo.version}version>
	dependency>
	
	
	<dependency>
	    <groupId>com.alibabagroupId>
	    <artifactId>dubbo-serialization-hessian2artifactId>
	    <version>${dubbo.version}version>
	dependency>
	
	
	<dependency>
	    <groupId>com.alibabagroupId>
	    <artifactId>dubbo-rpc-httpartifactId>
	    <version>${dubbo.version}version>
	dependency>
	
	
	<dependency>
	    <groupId>com.alibabagroupId>
	    <artifactId>dubbo-rpc-rmiartifactId>
	    <version>${dubbo.version}version>
	dependency>
	
	
	<dependency>
	    <groupId>com.alibabagroupId>
	    <artifactId>dubbo-rpc-hessianartifactId>
	    <version>${dubbo.version}version>
	dependency>

2.3 dubbo服务暴露过程主要流程梳理


3 dubbo服务消费过程底层原理探秘


3.1 spring环境下dubbo服务消费过程的前置知识

以注解配置为例,大家必须要有的前置知识是:当dubbo项目启动时,遇到@Reference(新版本的dubbo为@DubboReference)注解,会创建一个 ReferenceBean实例。该实例实现了InitializingBean 接口, 在其调用的afterPropertiesSet 方法中, 会为服务调用方 — 也就是@Reference标注的那个bean,创建一个远程代理对象。

创建过程中比较重要的两步源代码如下:


【1】获取可以调用服务端的服务,并将其和url、interface一起封装成一个Invoker
dubbo源码(1):消费端直连服务端的情况(在@Reference注解里可以配置直连的url)
在这里插入图片描述

dubbo源码(2):从注册中心获取服务端URL的情况
【dubbo源码解析】--- dubbo的服务暴露+服务消费(RPC调用)底层原理深入探析_第3张图片
这里需要注意的是:其实这一步已经获取到可以调用服务端的服务了,也就是说这个Invoker里封装了【可以调用服务端的服务】、【服务端的url】和【interface】。

获取【可以调用服务端的服务】的大致流程如下:
跟踪源码可以发现,无论是走http协议、rmi协议还是hessian协议【其他的协议我没具体研究】,它最终都会调用一个doRefer方法。我们这里简单看一下RMI协议的doRefer方法的源码:
【dubbo源码解析】--- dubbo的服务暴露+服务消费(RPC调用)底层原理深入探析_第4张图片
对比一下我的上篇文章《【dubbo源码解析~番外篇】— JDK和Spring的RMI使用姿势简介》,你就会豁然开朗了,原来这就是通过【服务端url】和【interface】查找到服务端暴露的服务啊。

当然这里我还是保留上篇文章第4部分的观点:

消费端咋就找到了服务端的服务,并且为啥在消费端一调用,就会直接调用到服务端的方法这一过程,无需过度探索。


【2】代理工厂拿着协议对象创建的Invoker,创建实际的代理类,该代理类最终就会成为@Reference注解标注的那个实例bean
在这里插入图片描述


3.2 实现一个自己的dubbo消费端

有了上面的知识后,其实我们也可以仿照dubbo的源码,自己实现一个消费端了。当然还是那句话本文仅仅是探索RPC远程调用的原理,所以一切从简。

仿照dubbo源码自己写的dubbo消费端:

@Test
public void clientRpc() {
    //获取具体的协议
    //  ---由URL确定到底使用哪个协议,默认情况下使用dubbo协议
    Protocol protocol = protocolLoader.getAdaptiveExtension();

    //由代理工厂生成Invoker对象
    //  --- Invoker对象里包含了服务的接口和要暴露服务的url
    //  --- 但值得注意的是这里返回的Invoker对象,是用Invoker接口进行接收的,也就是说通过下面的serviceInvoker,只能获取到service的接口
    Invoker<DemoService> referInvoker = protocol.refer(DemoService.class, protocol_url);

    //生成代理工厂
    //  --- 由URL确定到底是动态代理工厂(JdkProxyFactory)还是静态代理工厂(JavassistProxyFactory)
    //  --- 默认情况下为静态代理工厂
    ProxyFactory proxy = proxyLoader.getAdaptiveExtension();

    //生成DemoService的代理类
    DemoService service = proxy.getProxy(referInvoker);

    Map<String, String> info = new HashMap();
    info.put("target", "orderService");
    info.put("methodName", "getOrderInfo");
    info.put("arg", "1");
    Map<String, String> result = service.getHelloInfo(info);
    System.out.println(result);
}

3.3 dubbo消费端调用服务的主要流程梳理


4 简单测试我们自己写的服务端和消费端

启用服务端【我提供的代码可以测试的协议有http、rmi、dubbo、hessian】
【dubbo源码解析】--- dubbo的服务暴露+服务消费(RPC调用)底层原理深入探析_第5张图片
启用消费端调用服务端的服务

  • (1)可以看到已经获取到了服务端返回的结果
    【dubbo源码解析】--- dubbo的服务暴露+服务消费(RPC调用)底层原理深入探析_第6张图片
  • (2)同时可以看到确实调用到了服务端的服务【dubbo源码解析】--- dubbo的服务暴露+服务消费(RPC调用)底层原理深入探析_第7张图片

5 我心中Dubbo里最核心的概念: SPI/Invoker

  • (1)SPI
    通过本文的分析,相信你已经看到:【dubbo若想切换底层通讯协议】,【若想切换到底使用动态代理工厂还是静态代理工厂】是多么的easy!!! —> 只需要修改一个URL的参数,就搞定了,是不是很爽!!!

这里需要再提一下的是:

看完本文2.1.2的源码分析后,你会不会想到,其实dubbo使用注册中心的玩法,就是扩展了一个Protocol—>RegistryProtocol,然后只要你修改一下URL的参数【表现在dubbo实际使用中,就是修改一下配置文件+引入一下注册中心的jar】,就可以指向那个Protocol了 —》由此dubbo的核心源码还是本文提到的这些,但却可以将注册中心引入进来了—> 代码的扩展性多么好!!!

  • (2)Invoker
    其实Dubbo中的Invoker 概念, 作用不仅仅于此, 它统一了 dubbo 中各组件间相互交流的规范, 大家统一都用 invoker 进行粘合(书同文、 车同轴) 。 后续应该会继续展示 Invoker 更高一层的作用。。。

2020-07-15 夜

你可能感兴趣的:(dubbo知识点整理)