一、 服务暴露概述

dubbo的服务模型是非常简单的,要么是服务提供方(Provider)提供服务,要么是服务消费方(Consumer)消费服务,从dubbo官网的系统架构图就可以看出来。
Dubbo源码(四) 服务发布(上) - 图2

Provider与Consumer通过Registry来解耦合,这一点和Spring有点相似。在Spring中它的核心领域模型是Bean.我们通过配置bean,然后Spring容器获取到需要的对象。不需要关心对象的创建过程,并且我们可以在Spring的Bean的生命周期过程来来定制化对象。在使用dubbo的时候并不需要服务暴露与服务引用的细节。我们只需要在服务端配置:

provider-beans.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. http://www.springframework.org/schema/beans/spring-beans.xsd
  7. http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
  8. <dubbo:application name="demo-provider"/>
  9. <dubbo:registry address="multicast://224.5.6.7:1234"/>
  10. <dubbo:protocol name="dubbo" port="20880"/>
  11. <dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService"/>
  12. <bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl"/>
  13. </beans>
  14. 12345678910111213

在服务消费方法配置:

consumer-beans.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. http://code.alibabatech.com/schema/dubbo
  7. http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
  8. <dubbo:application name="demo-consumer"/>
  9. <dubbo:registry address="multicast://224.5.6.7:1234"/>
  10. <dubbo:reference id="demoService" interface="com.alibaba.dubbo.demo.DemoService"/>
  11. </beans>
  12. 1234567891011

就能够像本地方法调用一样,远程调用远程服务暴露的服务。

一、dubbo的领域对象

在dubbo当中它的核心领域对象就是Invoker,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它。
在dubbo中通过Protocol这个来管理Invoker的生命周期,包括服务的暴露与引用都是通过它来完成的。而在进行服务调用的时候通过Invocation来保存调用过程中的变量:包括方法名,参数等。所以在整个dubbo调用过程当中:

  • Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。
  • Protocol 是服务域,它是 Invoker 暴露和引用的主功能入口,它负责 Invoker 的生命周期管理。
  • Invocation 是会话域,它持有调用过程中的变量,比如方法名,参数等。

二、dubbo的服务暴露

Dubbo源码(四) 服务发布(上) - 图3

在服务提供者暴露的时候,首先从配置文件中获取到对外提供的实际类ref(如:HelloWorldImpl),然后通过ProxyFactory类(包括JavassistProxyFactory,JdkProxyFactory)把ref生成一个AbstractProxyInvoker实例,到这里就完成了具体服务到Invoker的转化 ,转化后的Invoke如下图所示:

Dubbo源码(四) 服务发布(上) - 图4

在Invoker中主要包括以下几个属性:

  1. 当前接口的Class实例(DemoService.class)
  2. 当前接口的实例类对象(DemoServiceImpl.class)
  3. URL,配置信息的统一格式,所有扩展点都通过传递 URL 携带配置信息
  4. 当前接口的代理对象Wrapper实例

下面就是Invoker转化成Exporter的过程。配置不同的协议dubbo暴露的方式就不一样,我们以默认的dubbo协议来说明。Dubbo协议把Invoke转化成Exporter主要是通过DubboExporter的export方法来进行暴露的。它主要是在当前机器通过配置的端口号打开socket监听服务,并收服务调用方发送过来的请求,默认通过netty来实现网络间的数据传输。

上面的服务暴露的对象转换是 dubbo 官方提供的,下面是具体的使用 zookeeper 为注册中心时, dubbo 的对象转换过程:

Dubbo源码(四) 服务发布(上) - 图5

如果大家看了后面的服务暴露的源码分析对于上面的图会有更加深刻的认识。

三、服务的注册

在服务的暴露过程中其实包括二个过程:一是把服务暴露通过配置相应的协议暴露到本地服务;二是把暴露的服务注册到暴露到配置的注册中心中去。其实dubbo服务暴露与注册主要是通过RegistryProtocol这个类来完成的。以dubbo为通信协议,zookeeper为注册中心举例。

Dubbo源码(四) 服务发布(上) - 图6

四、总结

在dubbo中屏蔽了远程调用的各个细节,抽象出了服务注册方(Provider)、注册中心(Registry)和服务消费方(Consumer),实现了服务发布与服务调用的解耦。并且做为服务调用可以以本地方法的形式来调用远程服务。关于服务发布与服务调用的上面只是说了服务暴露的主要的三个过程。其实在服务暴露的时候发出了以下几个动作:

  • 暴露本地服务
  • 暴露远程服务
  • 启动Netty
  • 连接Zookeeper
  • 到Zookeeper中注册
  • 监听zookeeper

二、 服务本地暴露

在上一篇文章我们分析了一下 dubbo 在服务暴露发生了哪些事,今天我们就来分析一下整个服务暴露中的本地暴露。(PS:其实我感觉本地暴露蛮鸡肋的)。本地暴露需要服务提供方与服务消费方在同一个 JVM。下面我们来写一个本地暴露使用的例子:

  1. DemoService.java
  1. public interface DemoService {
  2. String sayHello(String name);
  3. }
  1. DemoServiceImpl.java
  1. public class DemoServiceImpl implements DemoService {
  2. public String sayHello(String name) {
  3. return "Hello " + name + ", response form provider: " + RpcContext.getContext().getLocalAddress();
  4. }
  5. }
  1. application.xml – Spring配置文件
  1. <!-- 提供方应用信息,用于计算依赖关系 -->
  2. <dubbo:application name="demo-provider"/>
  3. <!-- 和本地bean一样实现服务 -->
  4. <bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl" />
  5. <!-- 声明需要暴露的服务接口 -->
  6. <dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" registry="N/A" scope="local"/>
  7. <dubbo:reference id="demoServiceDubbo" interface="com.alibaba.dubbo.demo.DemoService" injvm="true"/>
  1. Provider.java – 调用本地暴露的服务
  1. public class Provider {
  2. public static void main(String[] args) throws Exception {
  3. ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
  4. DemoService demoServiceDubbo = context.getBean("demoServiceDubbo", DemoService.class);
  5. String returnMessage = demoServiceDubbo.sayHello("carl");
  6. System.out.println(returnMessage);
  7. }
  8. }

使用context.getBean("demoServiceDubbo", DemoService.class)这种方式获取Bean是代理后的对象,context.getBean("demoService", DemoService.class)获取的是provider原始的spring的注入的实现类。前一种方式获取到是 dubbo 返回的代理类,其中可以获取到 dubbo 的InvokerListenerFilter这两个扩展点。下面我们就从源码的角度来分析一下 dubbo 的本地暴露吧。

如果大家看过 dubbo官网的API配置 (建议大家分析码源的时候都使用API的形式调用,尽量少的引入第三方Jar包比如xml配置dubbo),我们就可以知道。dubbo服务的暴露的起点是ServiceConfig#export。下面我们就以这个方法以起点来分析它:API方式调用见下
Dubbo 三种使用方式

Dubbo源码(四) 服务发布(上) - 图7

export这个方法就是dubbo服务暴露的入口,主要就是判断这个服务是否暴露以及通过ScheduledThreadPoolExecutor这个线程池类支持延迟暴露。

Dubbo源码(四) 服务发布(上) - 图8

接下来就是doExport方法,在这个方法的前面就是做一些 check 操作,不是重点,就不一一分析了。我们主要看一下它的appendProperties方法以及doExportUrls这两个方法。appendProperties()方法主要是为当前对象通过setter 方法来添加属性,它主要是通过以下方式来添加属性:

  • 从 System 中获取属性key值的优先通讯是: dubbo.provider. + 当前类 Id + 当前属性名称 > dubbo.provider. + 当前属性名称 为 key 获取值.
  • 首先从特定properties文件加载属性:首先 System.getProperty("dubbo.properties.file")获取到文件路径,如果获取不到就会试图加载 dubbo 的默认的路径 dubbo.properties加载。获取属性的 key 和上面从 System 里面获取的规则一样。

然后我们就来分析一下doExportUrl这个方法,因为 Dubbo 允许配置多协议,在不同服务上支持不同协议或者同一服务上同时支持多种协议。所以这里需要循环各个协议进行多协议暴露服务。

  1. private void doExportUrls() {
  2. List<URL> registryURLs = loadRegistries(true);
  3. for (ProtocolConfig protocolConfig : protocols) {
  4. doExportUrlsFor1Protocol(protocolConfig, registryURLs);
  5. }
  6. }

然后我们来分析一下ServiceConfig#doExportUrlsFor1Protocol,首先我们来看一下appendParameters(Map, Object)这个方法,这个方法的作用就是通过调用当前对象的getter方法获取到传入对象的值然后塞到 map 当中去。用于后面的构造URL这个对象。

  1. public final class URL implements Serializable {
  2. private static final long serialVersionUID = -1985165475234910535L;
  3. // 协议
  4. private final String protocol;
  5. // 用户名
  6. private final String username;
  7. // 密码
  8. private final String password;
  9. // 主机
  10. private final String host;
  11. // 端口
  12. private final int port;
  13. // path
  14. private final String path;
  15. // 其它参数
  16. private final Map<String, String> parameters;
  17. }

注:URL,配置信息的统一格式,所有扩展点都通过传递 URL 携带配置信息)

Dubbo源码(四) 服务发布(上) - 图9

如果大家没有看过我,dubbo源码分析 之 服务暴露概述 (见上),在这点我再向大家提醒一下,dubbo的对象扭转过程是:

服务配置类 —> Invoker —> Exporter

首先通过 ref (服务实现类)、服务接口类以及 URL 默认通过 javassist,也就是 JavassistProxyFactory类获取到代理对象。

Dubbo源码(四) 服务发布(上) - 图10

然后再把 Invoker 对象转化成Exporter对象。还记得之前的 dubbo源码分析 之 SPI分析 (见下) 之前是分析的远程暴露中的获取Protocol 实例。只是这里的 protocol实例是 本地方法暴露获取的实例。它也是 dubbo 自定义的 SPI 生成的 Protocol$Adaptive通过它的getExtension(name)方法创建 Protocol实例,然后通过 Protocol#export 方法获取Exporter
Dubbo源码(一) 内核SPI实现

Dubbo源码(四) 服务发布(上) - 图11

我们可以看到Protocol$Adaptive这个类生成的 Protocol对象的结构是:

  • ProtocolListenerWrapper
    • ProtocolFilterWrapper
      • InjvmProtocol

dubbo 的定义 SPI 里面包括 AOP,其实就是获取到所有的 SPI 接口的实例对象。然后在调用 getExtension(name)方法返回指定名字的扩展的时候会判断哪些实现类的构造器只包含 SPI 接口就会进行代理。这里的name是从 URL 中获取协议。在调用ServiceConfig#loadRegistries方法的里面返回的 URL 格式为registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?xxx=xxx然后再调用ServiceConfig#exportLocal(URL)方法的时候里面把协议(protocol)设置成injvm, 然后 在 Protocol$Adaptive 进行服务暴露的时候:

Dubbo源码(四) 服务发布(上) - 图12

因为 duubo 默认在dubbo-rpc-injvm的自定义Protocol配置文件(${dubbo-rpc-injvm}/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol)配置的是injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol所以得到的对象是InjvmProtocol。因为ProtocolListenerWrapperProtocolFilterWrapper 其实都包括 SPI 接口 Protocol的构造器。所以创建出来的对象如上所以。关于 dubbo 的SPI机制可以参看 dubbo源码分析 之 内核SPI实现 (见上)

  • ProtocolListenerWrapper : 通过 SPI 机制获取到 dubbo 的自定义扩展 InvokerListener
  • ProtocolFilterWrapper : 通过 SPI 机制获取到 dubbo 的自定义扩展 Filter(常用)。

其实关于 dubbo 通过 SPI 机制获取机制获取到 dubbo 的自定义扩展 Filter,还蛮复杂与重要的。在这里就不多说了,感兴趣的朋友可以下去参看源码。

最终就到了InjvmProtocol暴露服务,其实它就是创建一个 InjvmExporter 对象返回。里面包括 4 个属性:

  • invoker:Invoker 对象实例
  • key:服务 key 值,在进行服务调用的时候会根据这个 key 值。获取到当前服务暴露生成的 Exporter 对象。
  • exporterMap:服务 key 值与当前 Exporter 的映射

最后这个生成的 Exporter 最终会返回添加到 ServiceConfigexporters 属性当中去。这样就完成了 dubbo 服务暴露的本地暴露的过程。

Dubbo源码(四) 服务发布(上) - 图13

其实整个 dubbo 的本地暴露逻辑还是蛮简单的,把它分开来主要还是整个服务暴露过程比较复杂。先给大家讲解一下本地暴露。如果大家清楚了本地暴露,然后再理解远程暴露就会更加容易。