欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-12-20 14:59:19
本文对应源码地址:https://github.com/nieandsun/dubbo-study文章目录1 Protocol+Invoker简介1 Protocol+Invoker简介想要在一个JVM里调用另一个JVM里的的方法,或许你会想到如下的姿势: HttpClient RestTemplate WebService ServerSocket/Socket RMI — 可以参看我的上篇文章《【dubbo源码解析~番外篇】— JDK和Spring的RMI使用姿势简介》但是....

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



1 开篇

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

但是在真正进行方法调用时,你不仅要考虑传什么参数、调用哪个方法,还得考虑对方服务的暴露地址等。
而用过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)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调用)底层原理深入探析
看过我《【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包如下:

	<!--dubbo协议,该包里同时含有dubbo的Common等包-->
	<dependency>
	    <groupId>com.alibaba</groupId>
	    <artifactId>dubbo-rpc-dubbo</artifactId>
	    <version>${dubbo.version}</version>
	</dependency>	
	
	<!--dubbo协议底层用的netty,注意这里要用netty4-->
	<dependency>
	    <groupId>com.alibaba</groupId>
	    <artifactId>dubbo-remoting-netty4</artifactId>
	    <version>${dubbo.version}</version>
	</dependency>
	
	<!--dubbo协议底层序列化用的hessian2-->
	<dependency>
	    <groupId>com.alibaba</groupId>
	    <artifactId>dubbo-serialization-hessian2</artifactId>
	    <version>${dubbo.version}</version>
	</dependency>
	
	<!--http协议 该包里同时含有dubbo的Common等包-->
	<dependency>
	    <groupId>com.alibaba</groupId>
	    <artifactId>dubbo-rpc-http</artifactId>
	    <version>${dubbo.version}</version>
	</dependency>
	
	<!--rmi协议 该包里同时含有dubbo的Common等包-->
	<dependency>
	    <groupId>com.alibaba</groupId>
	    <artifactId>dubbo-rpc-rmi</artifactId>
	    <version>${dubbo.version}</version>
	</dependency>
	
	<!--hessian协议 该包里同时含有dubbo的Common等包-->
	<dependency>
	    <groupId>com.alibaba</groupId>
	    <artifactId>dubbo-rpc-hessian</artifactId>
	    <version>${dubbo.version}</version>
	</dependency>

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

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


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


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

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

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


【1】获取可以调用服务端的服务,并将其和url、interface一起封装成一个Invoker
dubbo源码(1):消费端直连服务端的情况(在@Reference注解里可以配置直连的url)
荐
                                                        【dubbo源码解析】--- dubbo的服务暴露+服务消费(RPC调用)底层原理深入探析

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

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

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

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


【2】代理工厂拿着协议对象创建的Invoker,创建实际的代理类,该代理类最终就会成为@Reference注解标注的那个实例bean
荐
                                                        【dubbo源码解析】--- dubbo的服务暴露+服务消费(RPC调用)底层原理深入探析


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消费端调用服务的主要流程梳理

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


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

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

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

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 夜

本文地址:https://blog.csdn.net/nrsc272420199/article/details/107269928