同理我们看下服务消费端启动流程时序图:

在《Dubbo整体架构分析》一文中,我们提到服务消费方需要使用ReferenceConfig API来消费服务,具体是调用代码(1)get()方法来生成远程调用代理类。get()方法最终会调用createProxy方法来具体创建代理类,其中createProxy结合时序图的核心代码如下:

@SuppressWarnings({"unchecked" , "rawtypes" , "deprecation"})
private T createProxy(Map<String , String> map){
...
if(isJvmRefer){
...
}else{
...
// (1) 当只配置一个服务中心的时候
if(urls.size() == 1){
invoker = refprotocol.refer(interfaceClass , urls.get(0));
}else{
// (2) 多个服务中心的时候
...
}
}
...
// (3) 创建代理服务
return (T)proxyFactory.getProxy(invoker);
}

其中refprotocol的定义如下:

private static final Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

可知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代码如下:

private <T> Invoker<T> doRefer(Cluster cluster , Registry registry , Class<T> type , URL url){
...
// (3)
directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY , Constants.PROVIDERS_CATEGORY + "," + Constants.CONFIGURATORS_CATEGORY + "," + Constants.ROUTERS_CATEGORY));
// (4)
return cluster.join(directory);
}

如上代码(3)的作用是向服务注册中心订阅服务提供者的服务,代码(4)则是调用扩展接口Cluster的适配器类的join方法,根据参数选择配置的集群容错策略。这里我们先讲讲代码(3)的逻辑,看看如何把服务消费方远程服务转换到Invoker,这里结合Zookeeper作为服务治理中心来讲解,首先看看时序图:

如上时序图步骤(2)(3)(4),从Zookeeper获取服务提供者的地址列表,等Zookeeper返回地址列表后会调用RegistryDirectory的notify方法,代码(6)(7)(8)根据获取的最新的服务提供者url地址转换为具体的invoker列表,也就是每个提供者的url会被转换为一个Invoker对象,具体转换在toInvokers方法中进行:

private Map<String , Invoker<T>> toInvokers(List<URL> urls){
Map<String , Invoker<T>> newUrlInvokerMap = new HashMap<String , Invoker<T>>();
...
for(URL providerUrl : urls){
...
Map<String , Invoker<T>> localUrlInvokerMap = this.urlInvokerMap; //
Invoker<T> invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.get(key);
if(invoker == null){
try{
...
if(enabled){
// 这里具体调用dubbo协议转换服务为invoker
invoker = new InvokerDelegete<T>(protocol.refer(serviceType , url) , url , providerUrl);
}
}catch(Throwable t){ }
if(invoker != null){
newUrlInvokerMap.put(key , invoker);
}
}else{
newUrlInvokerMap.put(key , invoker);
}
}
return newUrlInvokerMap;
}

如上代码,具体转换服务到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代码内容如下:

public <T> Invoker<T> refer(Class<T> serviceType , URL url) throws RpcException{
// create rpc invoker.
DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType , url ,getClients(url) , invokers);
invokers.add(invoker);
return invoker;
}

如上代码,首先getClients方法从(12)到(18)创建服务消费端的NettyClient对象用来连接服务提供者。另外可知refer方法内部返回了一个DubboInvoker,这个就是原生的invoker对象,服务方远程服务转换就是为了这个invoker。代码(12)则对这个invoker进行装饰,使用一系列filter形成了责任链,invoker被放到责任链的末尾,下面看看buildInvokerChain如何形成责任链,代码如下:

private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker , String key , String group){
Invoker<T> last = invoker;
// 获取所有激活的filter,然后使用链表方式形成责任链
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl() , key , group);
if(filters.size() > 0){
for(int i=filters.size()-1 ; i>=0 ; i--){
final Filter filter = filters.get(i);
final Invoker<T> next = last;
last = new Invoker<T>(){
public Class<T> getInterface(){
return invoker.getInterface();
}
public URL getUrl(){
return invoker.getUrl();
}
public boolean isAvailable(){
return invoker.isAvailable();
}
public Result invoke(Invocation invocation) throws RpcException{
return filter.invoke(next , invocation);
}
public void destroy(){
invoker.destroy();
}
@Override
public String toString(){
return invoker.toString();
}
};
}
}
return last;
}

其中扩展接口Filter对应的实现类,如下所示:

其中MonitorFilter和监控中心进行交互,FutureFilter用来实现异步调用,GenericFilter用来实现泛化调用,ActiveLimitFilter用来控制消费端最大并发调用量,ExecuteLimitFilter用来控制服务提供方最大并发处理量等,当然你可以写自己的filter。由于是责任链,所以ProtocolFilterWrapper的refer返回的是责任链头部的filter到ProtocolListenerWrapper,而ProtocolListenerWrapper的refer方法内容如下:

public <T> Invoker<T> refer(Class<T> type , URL url) throws RpcException{
...
return new ListenerInvokerWrapper<T>(protocol.refer(type,url) , Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(InvokerListener.class).getActivateExtension(url , Constants.INVOKER_LISTENER_KEY)));
}

ProtocolListenerWrapper的refer返回的是ListenerInvokerWrapper对象,所以代码(9)返回的也是url对应的ListenerInvokerWrapper对象,然后再回到时序图代码(8)的toInvokers方法,可知toInvokers返回的是使用InvokerDelegete对ListenerInvokerWrapper包裹的对象。到这里RegistryDirectory里就维护了所有服务者的invoker列表,消费端发送信息时就是根据集群容错和负载均衡算法从invoker列表里选择一个进行调用,当服务提供者挂了的时候,Zookeeper会通知更新这invoker列表。

到这里我们讲完了本节第一个时序图中的步骤(3),下面我们接着讲解步骤(4) 。

默认下步骤(4)会调用FailoverCluster的join方法,FailoverCluster的join方法代码如下:

public class FailoverCluster implements Cluster{
private static final String NAME = "failover";
public <T> Invoker<T> join(Directory<T> directory) throws RpcException{
return new FailoverClusterInvoker<T>(directory);
}
}

这里把directory对象包裹到了FailoverClusterInvoker,这里需要注意下directory就是上面讲解的RegistryDirectory,其内部维护了所有服务提供者的invoker列表,而FailoverCluster就是集群容错策略。

其实Dubbo对cluster扩展接口实现类使用wrapper类MockClusterWrapper进行增强,这个从下图可以得到证明:

实际上的调用时序图如下图所示:

该时序图中(3)返回了FailbackClusterInvoker对象到(2),下面看看MockClusterWrapper的代码:

public class MockClusterWrapper implements Cluster{
private Cluster cluster;
public MockClusterWrapper(Cluster cluster){
this.cluster = cluster;
}
public <T> Invoker<T> join(Directory<T> directory) throws RpcException{
return new MockClusterInvoker<T>(directory , this.cluster.join(directory));
}
}

可知MockClusterWrapper类把FailoverClusterInvoker包装成了MockClusterInvoker实例,所以整个调用链最终调用返回的是MockClusterInvoker对象。也就是本文第一个时序图步骤(4)返回的是MockClusterWrapper。然后执行代码(13)获取MockClusterInvoker的代理实现invoker到客户端接口的转换,这里默认调用的是JavassistProxyFactory的getProxy方法,代码如下:

public <T> T getProxy(Invoker<T> invoker , Class<?>[] interfaces){
return (T)Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}

其中InvokerInvocationHandler为具体拦截器。至此我们按照逆序的方式把服务消费端启动流程讲解完毕,下面在顺过来讲解一次远程调用过程。

Dubbo服务消费端一次调用流程原理分析

同理先上时序图:

由于服务消费端通过ReferenceConfig的get()方法返回的是一个代理类,并且方法拦截器为InvokerInvocationHandler,所以当调用了服务的方法后会被InvokerInvocationHandler拦截,执行如上时序图流程。

如上流程,首先步骤(1)(2)(3)调用了集群容错策略FailoverClusterInvoker,其内部首先根据设置的负载均衡策略选择一个invoker作为FailoverClusterInvoker具体的远程调用者,如果调用发生异常,则根据FailoverClusterInvoker的策略重新选择一个invoker进行调用。

FailoverClusterInvoker内每次调用具体invoker的invoke方法都会走到步骤(8)(9),然后步骤(10)(11)(12)是filter内创建的责任链,最后调用了原生的DubboInvoker,具体使用nettyclient与服务提供者进行交互。

  

Dubbo学习笔记10:Dubbo服务消费方启动流程源码分析的更多相关文章

  1. Dubbo学习笔记9:Dubbo服务提供方启动流程源码分析

    首先我们通过一个时序图,直观看下Dubbo服务提供方启动的流程: 在<Dubbo整体框架分析>一文中我们提到,服务提供方需要使用ServiceConfig API发布服务,具体是调用代码( ...

  2. Dubbo学习笔记4:服务消费端泛化调用与异步调用

    本文借用dubbo.learn的Dubbo API方式来解释原理. 服务消费端泛化调用 前面我们讲解到,基于Spring和基于Dubbo API方式搭建简单的分布式系统时,服务消费端引入了一个SDK二 ...

  3. OpenCV学习笔记(27)KAZE 算法原理与源码分析(一)非线性扩散滤波

    http://blog.csdn.net/chenyusiyuan/article/details/8710462 OpenCV学习笔记(27)KAZE 算法原理与源码分析(一)非线性扩散滤波 201 ...

  4. 【图解源码】Zookeeper3.7源码分析,包含服务启动流程源码、网络通信源码、RequestProcessor处理请求源码

    Zookeeper3.7源码剖析 能力目标 能基于Maven导入最新版Zookeeper源码 能说出Zookeeper单机启动流程 理解Zookeeper默认通信中4个线程的作用 掌握Zookeepe ...

  5. Android笔记--View绘制流程源码分析(二)

    Android笔记--View绘制流程源码分析二 通过上一篇View绘制流程源码分析一可以知晓整个绘制流程之前,在activity启动过程中: Window的建立(activit.attach生成), ...

  6. Android笔记--View绘制流程源码分析(一)

    Android笔记--View绘制流程源码分析 View绘制之前框架流程分析 View绘制的分析始终是离不开Activity及其内部的Window的.在Activity的源码启动流程中,一并包含 着A ...

  7. 10.4 android输入系统_框架、编写一个万能模拟输入驱动程序、reader/dispatcher线程启动过程源码分析

    1. 输入系统框架 android输入系统官方文档 // 需FQhttp://source.android.com/devices/input/index.html <深入理解Android 卷 ...

  8. Netty入门一:服务端应用搭建 & 启动过程源码分析

    最近周末也没啥事就学学Netty,同时打算写一些博客记录一下(写的过程理解更加深刻了) 本文主要从三个方法来呈现:Netty核心组件简介.Netty服务端创建.Netty启动过程源码分析 如果你对Ne ...

  9. dubbo学习笔记二(服务调用)

    项目结构 代码示例 由于之前的IEchoService 的一个方法只是在服务端控制台打印,不便在浏览器测试,所以新添加的方法 api和服务端代码变更 public interface IEchoSer ...

随机推荐

  1. 浅谈 iOS 中的 Activity Indicator

    Activity Indicator 是iOS开发中必不可少的一个视图.本文就简单地总结一下这个Activity Indicator 的使用方法. 默认 Activity Indicator 以下的函 ...

  2. http-cache浏览器缓存

    摘至知乎 首先得明确 http 缓存的好处 减少了冗余的数据传输,减少网费 减少服务器端的压力 Web 缓存能够减少延迟与网络阻塞,进而减少显示某个资源所用的时间 加快客户端加载网页的速度 常见 ht ...

  3. Netty源码分析(前言, 概述及目录)

    Netty源码分析(完整版) 前言 前段时间公司准备改造redis的客户端, 原生的客户端是阻塞式链接, 并且链接池初始化的链接数并不高, 高并发场景会有获取不到连接的尴尬, 所以考虑了用netty长 ...

  4. Linux内核分析作业第三周

    一.实验楼实验 使用实验楼的虚拟机打开shell 1 cd LinuxKernel/ 2 qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd ...

  5. ns3的输入输出奥秘(一) LOGGING系统

    1.LOGGING系统 (1)在我们之前对C++的理解,输出好像就是cout,然而 以myfirst.cc为例子 在我们前面的编写的代码中并没有出现cout,那他是如何输出. 可以回忆一下 LogCo ...

  6. sql 索引 sql_safe_updates

    为了数据的安全性,mysql有一个安全性设置,sql_safe_updates ,当把这个值设置成1的时候,当程序要对数据进行修改删除操作的时候条件必须要走索引. 刚好现在也碰到了此类问题:网上找了相 ...

  7. Python网络编程:IO多路复用

    io多路复用:可以监听多个文件描述符(socket对象)(文件句柄),一旦文件句柄出现变化,即可感知. sk1 = socket.socket() sk1.bind(('127.0.0.1',8001 ...

  8. 一本通1656Combination

    1656:Combination 时间限制: 1000 ms         内存限制: 524288 KB [题目描述] 原题来自:BZOJ 2982 LMZ 有 n 个不同的基友,他每天晚上要选  ...

  9. sqlbulkcopy 批量更新 数据库

    转载: http://blog.csdn.net/wangzh300/article/details/7382506 private static void DataTableToSQLServer( ...

  10. nginx mp3

    location /mp3 { alias "d:/mp3"; default_type audio/mpeg; autoindex on; }