- 深度剖析ApacheDubbo核心技术内幕
- 翟陆续
- 16字
- 2025-02-17 23:22:20
第3章 远程服务发布与引用流程剖析
3.1 Dubbo服务发布端启动流程剖析
首先我们通过一个时序图,直观地看看Dubbo服务提供方启动的流程,如图3.1所示。

图3.1
在2.2节中我们提到,服务提供方需要使用ServiceConfig API发布服务,具体来说就是执行图3.1中步骤1的export()方法来激活发布服务。export()方法的核心代码如下:通过上面的代码可知,Dubbo的延迟发布是通过使用ScheduledExecutorService来实现的,可以通过调用ServiceConfig的setDelay(Integer delay)方法来设置延迟发布时间,其中shouldDelay()方法的代码如下:


如果没有设置延迟时间,则直接调用doExport()方法发布服务;如果设置了延迟发布,则等时间过期后调用doExport()方法来发布服务。
图3.1中的步骤2主要是根据ServiceConfig里面的属性进行合法性检查,这里我们主要看其内部最后调用的doExportUrls()方法。
图3.1中的步骤4通过调用loadRegistries()方法加载所有的服务注册中心对象,在Dubbo中,一个服务可以被注册到多个服务注册中心。
图3.1中的步骤5是在doExportUrlsFor1Protocol()方法内部首先把参数封装为URL(在Dubbo里会把所有参数封装到一个URL里),然后具体执行服务导出,其核心代码如下:代码1解析MethodConfig对象设置的方法级别的配置并保存到参数map中;代码2用来判断调用类型,如果为泛型调用,则设置泛型类型(true、nativejava或bean方式);代码5用来导出服务,Dubbo服务导出分本地导出与远程导出,本地导出使用了injvm协议,是一个伪协议,它不开启端口,不发起远程调用,只在JVM内直接关联,但执行Dubbo的Filter链;在默认情况下,Dubbo同时支持本地导出与远程导出协议,可以通过ServiceConfig的setScope()方法设置,其中配置为none表示不导出服务,为remote表示只导出远程服务,为local表示只导出本地服务。


如果使用MetadataReportConfig设置了元数据存储信息,代码5.2.3则将元数据保存到指定配置中心。在Dubbo 2.7.0中对服务元数据进行了改造,其把原来都保存到服务注册中心的元数据进行了分类存储,注册中心将只用于存储关键服务信息,比如服务提供者地址列表、完整的接口定义等。Dubbo 2.7.0使用更专业的配置中心,如Nacos、Apollo、Consul和Etcd等,提供更灵活、更丰富的配置规则,包括服务和应用不同粒度的配置、更丰富的路由规则和集中式管理的动态参数规则等。
另外,服务提供端导出服务具体使用的是下列代码:

其中,proxyFactory和protocol的定义为:

由此可知,proxyFactory和protocol都是扩展接口的适配器类。
执行代码proxyFactory.getInvoker(ref,(Class)interfaceClass,url)时,我们发现实际上是首先执行扩展接口ProxyFactory的适配器类ProxyFactory$Adaptive的getInvoker()方法,其内部根据URL里的proxy的类型选择具体的代理工厂,这里默认proxy类型为javassist,所以又调用了JavassistProxyFactory的getInvoker()方法获取了代理类。
JavassistProxyFactory的getInvoker()方法的代码如下:

根据前面章节介绍的内容可知,这里首先把服务实现类转换为Wrapper类,是为了减少反射的调用,这里返回的是AbstractProxyInvoker对象,其内部重写doInvoke()方法,并委托给Wrapper实现具体功能。到这里就完成了2.2节中讲解的服务提供方实现类到Invoker的转换。
另外,当执行protocol.export(wrapperInvoker)方法的时候,实际调用了Protocol的适配器类Protocol$Adaptive的export()方法。如果为远程服务暴露,则其内部根据URL中Protocol的类型为registry,会选择Protocol的实现类RegistryProtocol。如果为本地服务暴露,则其内部根据URL中Protocol的类型为injvm,会选择Protocol的实现类InjvmProtocol。但由于Dubbo SPI的扩展点使用了Wrapper自动增强,这里就使用了ProtocolFilterWrapper、ProtocolListenerWrapper、QosProtocolWrapper对其进行了增强,所以需要一层层调用才会调用到RegistryProtocol的export()方法。本节我们只讲解远程服务暴露流程。
图3.1中步骤10的RegistryProtocol中的export()方法通过步骤11的doLocalExport启动了NettyServer进行监听服务,步骤12、步骤13则将当前服务注册到服务注册中心。到这里就完成了2.2节中谈到的Invoker到Exporter的转换。
下面我们首先看看doLocalExport是如何启动NettyServer的。doLocalExport内部主要调用DubboProtocol的export()方法,下面看看如图3.2所示的时序图。

图3.2
从图3.2可以看到,在RegistryProtocol的doLocalExport()方法内调用了Protocol的适配器类Protocol$Adaptive,这里URL内的协议类型是dubbo,所以返回的SPI扩展实现类是DubboProtocol,由于DubboProtocol也被Wrapper类增强了,所以也是一层层调用后,才执行到图3.2中的步骤8,即调用DubboProtocol的export()方法。export()方法的代码如下:

这里将Invoker转换为DubboExporter对象,并且把DubboExporter保存到了缓存exporterMap里(在服务提供方处理请求时会从中获取出来),然后执行图3.2中步骤10的openServer()方法,其代码如下:


通过上面的代码可知,这里首先是获取当前机器地址信息(ip:port)并作为key,然后判断当前是否为服务提供端。如果是,则以此key为key查看缓存serverMap中是否有对应的Server,如果没有则调用createServer()方法来创建,否则返回缓存中的value。由于每个机器的ip:port是唯一的,所以多个不同服务启动时只有第一个会被创建,后面的服务都是直接从缓存中返回的。
接着一步步会执行到如图3.3所示的时序图中的步骤7,即NettyTransporter的bind()方法,这里由于Transporter是扩展接口,所以需要先经过其适配器类Transporter$Adaptive来根据URL里的参数做选择扩展实现。

图3.3
在默认情况下,传输扩展实现选择的是Netty,而Netty对应的扩展实现为:

下面,我们看看NettyTransporter的bind()方法,其代码如下:

NettyServer的构造函数代码如下:


在NettyServer的构造函数内部又调用了其父类AbstractServer的构造函数,后者构造函数的内容如下:

通过上面的代码可知,NettyServer的doOpen()方法启动了服务监听,其中doOpen()方法的代码如下:


至此,服务提供方的NettyServer已经启动了,这里需要注意是,将NettyServerHandler和编解码Handler注册到了每个接收链接的通道(channel)管理的管线中,这些Handler的详细说明在后面的章节会具体讲解。
最后,我们看看在启动NettyServer后如何将服务注册到服务注册中心,这里我们使用ZooKeeper作为服务注册中心。接着上面的图3.1中步骤12的流程,我们先看看RegistryProtocol中export()方法里的getRegistry()方法是如何获取服务注册中心和注册服务的,首先看看如图3.4所示的时序图。
图3.4中的步骤2用来获取服务注册中心,其中RegistryFactory为扩展接口,所以这里通过适配器类来确定RegistryFactory的扩展实现为ZookeeperRegistryFactory,然后后者内部调用createRegistry()方法创建了一个ZookeeperRegistry作为ZooKeeper注册中心。其中ZookeeperRegistry的getRegistry()方法的代码如下:

图3.4


在图3.4的步骤6中,register()方法最终调用了zkClient的create()方法将服务注册到ZooKeeper:

其中,create()方法的代码如下:

比如,以com.books.dubbo.demo.api.GreetingService服务接口为例,则其URL为:


经过toUrlPath转换后为:

create()方法是递归函数,首先其调用了方法createPersistent()分别创建了节点/dubbo、/dubbo/com.books.dubbo.demo.api.GreetingService和/dubbo/com.books.dubbo.demo.api.GreetingService/providers,然后调用createEphemeral()方法创建了下列内容:

服务注册到ZooKeeper后,ZooKeeper服务端的最终树图结构如图3.5所示。

图3.5
通过图3.5可知,机器192.168.0.1作为com.books.dubbo.demo.api.GreetingService服务的提供者,第一层Root节点说明ZooKeeper的服务分组为Dubbo,第二层Service节点说明注册的服务为com.books.dubbo.demo.api.GreetingService接口,第三层Type节点说明是为服务提供者注册的服务,第四层URL记录服务提供者的地址信息(这里只是简单地显示了服务提供者的IP信息,对于真实情况则不只是IP信息)。
第一个服务提供者注册时需要ZooKeeper服务端创建第一层的Dubbo节点、第二层的Service节点、第三层的Type节点,但是同一个Service的其他机器在注册服务时因为上面三层节点已经存在了,所以只需在Providers下也就是第四层插入服务提供者信息节点就可以了。
服务注册到ZooKeeper后,消费端就可以在Providers节点下找到com.books.dubbo.demo.api.GreetingService服务的所有服务提供者,然后根据设置的负载均衡策略选择机器进行远程调用了。