![深度剖析ApacheDubbo核心技术内幕](https://wfqqreader-1252317822.image.myqcloud.com/cover/296/29126296/b_29126296.jpg)
3.3 Dubbo服务消费方启动流程剖析
我们看看服务消费方启动流程时序图(如图3.8所示)。
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/94_1.jpg?sign=1738886971-ctIoGbQF4y6gHEHhDjSxLTuuvHz6oD7I-0-a78d3533e7c55f2212973b28eeb01427)
图3.8
在2.2节中,我们提到服务消费方需要使用ReferenceConfig API来消费服务,这里就是执行图3.8中步骤1的get()方法来生成远程调用代理类。get()方法首先会执行init()方法:其中,checkMock()方法用来检查设置的mock是否正确(有关细节我们在第5章会展开讲解),然后通过调用createProxy()方法来创建代理类,createProxy()方法的核心代码如下:
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/95_1.jpg?sign=1738886971-NXutSEO5SGi28ciL971eMPsGgPht4jye-0-3514186375a773d069fc7425f7a5e7e2)
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/95_2.jpg?sign=1738886971-zZAuJA2MTv78yTAht2LKJGLXjhPeHz6U-0-ca263604cdfd44abe375eaefe66ef315)
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/96_1.jpg?sign=1738886971-mpnUu8KpvSstSIUTeMHCDlL8pRSRW3Vr-0-f32717e2f613474a24117f333ca60e6a)
上面的代码比较简单,首先判断是否需要开启本地引用,如果需要则创建JVM协议的本地引用,然后加载服务注册中心信息,服务注册中心可以有多个。
在2.2节我们讲过,服务暴露第一步是调用Protocol扩展接口实现类的refer()方法以生成Invoker实例,这正是代码4做所的事情,其中refprotocol的定义如下:
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/97_1.jpg?sign=1738886971-7ayuyRsRAa1Eg80WSk1G5sWX3kPbQJEO-0-46e85f4ab238cda9af9023307ec67dea)
从上面的代码可知,refprotocol是Protocol扩展接口的适配器类,这里调用的refprotocol.refer(interfaceClass,urls.get(0));实际上是Protocol$Adaptive的refer()方法。
在Protocol$Adaptive的refer()方法内部,当我们设置了服务注册中心后,可以发现当前协议类型为registry,也就是说这里需要调用RegistryProtocol的refer()方法。但RegistryProtocol被QosProtocolWrapper、ProtocolFilterWrapper、ProtocolListenerWrapper三个Wrapper类增强了,所以这里经过一层层调用后,最后才调用到RegistryProtocol的refer()方法,其内部主要是调用了doRefer()方法,doRefer()方法的代码如下:
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/97_2.jpg?sign=1738886971-LXsBtAZUqSSEkZh5R1wUWQcdOTVgjyFH-0-89a0b64d440275fd8569765172c723c5)
代码1根据订阅的URL创建路由规则链,代码2的作用是向服务注册中心订阅服务提供者的服务,代码3则是调用扩展接口Cluster的适配器类的join()方法,根据参数选择配置的集群容错策略。这里我们先讲讲代码2的逻辑,看看Invoker是如何生成的,这里结合ZooKeeper作为服务注册中心来讲解,首先看看如图3.9所示的时序图。
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/98_1.jpg?sign=1738886971-gZderMm9RUDNZqON3b2dhR96CdUifgvh-0-6b49b5ad1732266fc834014303158f5f)
图3.9
图3.9中的步骤2、步骤3和步骤4用来从ZooKeeper获取服务提供者的地址列表,等ZooKeeper返回地址列表后会调用RegistryDirectory的notify()方法:
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/98_2.jpg?sign=1738886971-4ZWlgLWEH2y8VAfwsEqHijE4LMxFz62p-0-509faa33214d60fe8d53e236670ce7a0)
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/99_1.jpg?sign=1738886971-ZrdPr2mGJEHX6ALLUeNBesVGE3tOHnMr-0-e69918d7437c2f7c37d6579ea88b3d4a)
在上面代码的notify()方法内,对元数据信息进行了分类保存。
图3.9中的步骤6、步骤7和步骤8根据获取的最新的服务提供者URL地址,将其转换为具体的invoker列表,也就是说每个提供者的URL会被转换为一个Invoker对象,具体转换在toInvokers()方法中进行:
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/99_2.jpg?sign=1738886971-G6fVB6deLrszVX6YgrY6NQe1lg8d0yHT-0-5c3eb48d100ca0ec3aa0bc5c7fa0cd95)
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/100_1.jpg?sign=1738886971-3Y9svLHzJUJIx9chEexdWSnNoIrWUdKH-0-3968392ef9e965405658da2fa341c509)
从上面的代码可知,将服务接口转换到invoker对象是通过调用protocol.refer(serviceType,url)来完成的,这里的protocol对象也是Protocol扩展接口的适配器对象,所以调用protocol.refer实际上是调用适配器Protocol$Adaptive的refer()方法。在URL中,协议默认为是Dubbo,所以适配器里调用的应该是DubboProtocol的refer()方法。
前面的章节已讲过,Dubbo默认提供了一系列Wrapper类对扩展实现类进行功能增强,当然这里也不例外,Dubbo使用了ProtocolListenerWrapper、ProtocolFilterWrapper等类对DubboProtocol进行了功能增强。所以,在这里经过一次次调用后才调用到DubboProtocol的refer()方法,DubboProtocol的refer()方法的代码如下:
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/100_2.jpg?sign=1738886971-IAtB925jM9JX7miv6w1Uu5vnvVrJUlii-0-cf69ce07dc486aacc541592fc3360cf6)
从上面的代码可知,getClients()方法创建服务消费端的NettyClient对象,其调用链的时序图如图3.10所示,其中NettyClient构造函数如下:
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/100_3.jpg?sign=1738886971-hCLhOqAgrh6iRWmtKkPwMlCg6bPD1PEz-0-44126c84b5b486589dbe0b1fa4d9a264)
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/101_1.jpg?sign=1738886971-XKpesuOX8XXtho2xPUZOUQ1wWsBlg1k4-0-f27ac7aa428a120fe62fbfe5e454a840)
在ChannelHandlers.wrap函数内会确定消费端Dubbo内部的线程池模型(关于线程池模型,在后面的章节会做具体讲解)。
NettyClient的父类AbstractClient的构造函数如下:
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/101_2.jpg?sign=1738886971-aJyup1Hb4QuDowhMnxei0mHuKAOhEn0y-0-f7e4365855dc712a69f9cda1287eea90)
在上面的代码中,首先调用了NettyClient的doOpen()方法:
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/101_3.jpg?sign=1738886971-IsB5iCnXdolT9On8ir0jAjCaPVQE8E2I-0-25295eb26f3e89679781661b9bfa760b)
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/102_1.jpg?sign=1738886971-l9qBcQoa9mwjZeNx229UAvEiJWcQo8Kg-0-9b8d6b938e74df95148b7c8474b858c7)
上面的代码创建了一个启动NettyClient的bootstrap并对其进行设置,这里是把编解码器和自定义的nettyClientHandler添加到了链接Channel对应的管线里,在后面的第13章会做具体讲解。
在调用doOpen()方法后会调用NettyClient的doConnect()方法与服务提供者建立TCP链接,其中NettyClient的doConnect()方法的代码如下:
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/102_2.jpg?sign=1738886971-lJnv9PRGpzYOoQ46qcLCmNxjwN0BWccR-0-038588650a3a5f425a5723db3f27251f)
另外需要注意的是,在NettyClient的父类AbstractEndpoint中确定了编解码器,这里默认为DubboCodec:
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/102_3.jpg?sign=1738886971-xHYjrxi1X01W82xUsFtxRWMLUiks9OTs-0-3f870f9f212f4f61d8058dda05d1ed69)
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/103_1.jpg?sign=1738886971-bVFWVxgsoyXmShgzbkOZGlSxnqecOs8S-0-5c4ccefb0342f79df5e7d636bc475506)
然后,在doOpen()方法中,代码NettyCodecAdapter adapter=new NettyCodecAdapter(getCodec(),getUrl(),NettyClient.this);使用getCodec()方法获取了该编解码器并封装到NettyCodecAdapter适配器中,然后把编解码器设置到链接Channel的管线中。关于编解码器的使用,后面有专门的章节进行讲解。
这里需要注意三点:第一点,由于同一个服务提供者机器可以提供多个服务,那么消费者机器需要与同一个服务提供者机器提供的多个服务共享连接,还是与每个服务都建立一个连接?第二点,消费端是启动时就与服务提供者机器建立好连接吗?第三点,每个服务消费端与服务提供者集群中的所有机器都有连接吗?对于第三点,我们可以看看图3.9中的toRouters()方法就可以找到答案,其内部是把具体服务的所有服务提供者的URL信息转换为了Invoker,也就是说服务消费端与服务提供者的所有机器都有连接。
为了回答上面的问题,我们看看getClients()方法的代码:
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/103_2.jpg?sign=1738886971-4WYl3TvtIe1r5ZlRDB1maHhFJtxEc52M-0-6d30175c5481260f5c8b6c9d77d41e66)
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/104_1.jpg?sign=1738886971-QtiTSCLTNKtFzV2cTVt5db8pQ83jtWDM-0-f17a06fefc2990e0dca26b16abfbef1f)
通过上面的代码可知,在默认情况下当消费端引用同一个服务提供者机器上多个服务时,这些服务复用一个Netty连接,这里回答了第一个问题。
下面我们从initClient()方法里看第二个问题的答案:
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/104_2.jpg?sign=1738886971-n3GVz7HqUnZidSJSsj1tD9JFw6KdD7PU-0-6b66e38415a8c986aa63eb033a1afbc8)
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/105_1.jpg?sign=1738886971-dvyWuerKkhlQYsgzPvHIx91b4uSFk3l5-0-b5d3f3ad1cff79b57163a4158a6736c5)
上面的代码默认lazy为false,所以当消费端启动时就与提供者建立了连接,这里回答了第二个问题。
另外,从DubboProtocol的refer()方法可知,其内部返回了一个DubboInvoker,这就是原生的invoker对象,服务消费方远程服务转换就是为了这个invoker。图3.9中的步骤17则是对这个invoker进行装饰,即使用一系列Filter形成了责任链,invoker被放到责任链的末尾。下面我们看看ProtocolFilterWrapper的buildInvokerChain()方法是如何形成责任链的,具体代码如下:
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/105_2.jpg?sign=1738886971-P4n1TqGgGCll3bplAmM9M6DNvJ3a8v1C-0-0d8aa9107f7e7bc8211f44b9b224117c)
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/106_1.jpg?sign=1738886971-RXQvHensZoGuOJ5w70Gdl5lovBKzoQfC-0-0d2864f29d819b3b56334d1c4bbab7c0)
其中,扩展接口Filter对应的实现类如下所示:
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/106_2.jpg?sign=1738886971-QRErmvTotVmaVk4AxAitXcpAJmzQDeJj-0-66a20e1bfdf0f91eec580df1eaf18425)
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/107_1.jpg?sign=1738886971-rSlGENUvWcShKQR7nps7VzvompkAVSqX-0-0be6756870406672a2174feb246a8072)
其中,MonitorFilter和监控中心进行交互,FutureFilter用来实现异步调用,GenericFilter用来实现泛化调用,ActiveLimitFilter用来控制消费端最大并发调用量,ExecuteLimitFilter用来控制服务提供方最大并发处理量等,当然也可以写自己的Filter。
需要注意的是,消费端启动时并不是把所有的Filter扩展实现都放到责任链中,而是把group=consumer并且value值在URL里的才会放到责任链中,这一点在2.5节中提到过。
由于是责任链,所以ProtocolFilterWrapper的refer()方法是将责任链头部的Filter返回到ProtocolListenerWrapper。ProtocolListenerWrapper的refer()方法的代码如下:
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/107_2.jpg?sign=1738886971-dk7SktScTSyWwB0VR9I0ymNUee8b5ky3-0-9f63ce55d76a24e500e9f76f750ddc9c)
至此可知,在图3.10中,在RegistryDirectory里维护了所有服务者的invoker列表,消费端发起远程调用时就是根据集群容错和负载均衡算法以及路由规则从invoker列表里选择一个进行调用的,当服务提供者宕机的时候,ZooKeeper会通知更新这个invoker列表。
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/107_3.jpg?sign=1738886971-v0weh5OkshzHhdQeUshAtFZlLdOf5yBE-0-210060f31f10a8a41b774968e6620c5d)
图3.10
到这里,我们就讲完了图3.9中directory.subscribe()方法是如何订阅服务提供者服务的,并且把服务提供者所有的URL信息转换为了invoker列表,并保存到RegistryDirectory里。下面,我们接着讲解图3.9中RegistryProtocol的doRefer()方法中的cluster.join(directory)是如何使用集群容错扩展将Dubbo协议的invoker客户端转换为需要的接口的。在默认情况下,cluster的扩展接口实现为FailoverCluster,所以这里是调用FailoverCluster的join()方法,FailoverCluster的join()方法的代码如下:
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/108_1.jpg?sign=1738886971-PJoRTbwYYWY8muGn9Go9Choi1WjvC7CI-0-a3fde34c85839c28d6392e7910701fc7)
这里是把directory对象包裹到了FailoverClusterInvoker里,需要注意的是,directory就是上面讲解的RegistryDirectory,其内部维护了所有服务提供者的invoker列表,而FailoverCluster就是集群容错策略。
其实,Dubbo对cluster扩展接口实现类使用Wrapper类MockClusterWrapper进行增强,这一点从图3.11可以得到证明。
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/108_2.jpg?sign=1738886971-GTq8ZpyFoMUoIETO6ifLEm1PhlSp8RGU-0-c266f68aa42fde63551e0b1292610ca4)
图3.11
实际上调用的时序图如图3.12所示。
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/109_1.jpg?sign=1738886971-sy0jTgYeOArOCbyipTEuaseaOjU7Maan-0-f12c49dad4af68dac02a153f49a4b921)
图3.12
图3.12中的步骤3将FailbackClusterInvoker对象返回到步骤2,下面看看MockClusterWrapper类的代码:
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/109_2.jpg?sign=1738886971-XDpELgl8Jz5hOJVsBkyym9Sw2GMvSN6V-0-464545980dc280ab753ad5403f1bb2bd)
从上面的代码可知,MockClusterWrapper类把FailoverClusterInvoker包装成了MockClusterInvoker实例,所以整个调用链最终调用返回的是MockClusterInvoker对象。也就是说,本节第一个时序图(见图3.8)中的步骤4返回的是MockClusterWrapper,然后执行图3.9中的步骤14以获取MockClusterInvoker的代理,实现invoker到客户端接口的转换,这里默认调用的是JavassistProxyFactory的getProxy()方法,其代码如下:
![](https://epubservercos.yuewen.com/982FB6/16264452805769106/epubprivate/OEBPS/Images/109_3.jpg?sign=1738886971-27AihBayqgs6E6agPpTtI48kPo4Ce8Yc-0-52d911009b86723569cb07ce1207c710)
其中,InvokerInvocationHandler为具体拦截器。至此,我们按照逆序的方式把服务消费端启动流程讲解完了,下面一节讲解一次远程调用过程。