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

在《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. SpringBoot日记——MQ消息队列整合(二)

    基于第一篇文章搭建好环境以后,我们这篇文章继续介绍如何在springboot中使用RabbitMQ. 1).单播:添加好pom文件和自定义配置后,来看: @Autowired RabbitTempla ...

  2. Monkey基本使用

    什么是 Monkey Monkey 是一个 Android 自动化测试小工具.主要用于Android 的压力测试, 主要目的就是为了测试app 是否会Crash. Monkey 特点 顾名思义,Mon ...

  3. DES的加密与解密算法(Python实现)

    DES的加密与解密算法(Python实现) 密码学实验:实现了DES的简单的加密和解密算法,DES算法的相关资料网上很多,这里不再赘述,仅仅贴出源代码给大家分享,源码中包含很多汉字注释,相信大家都是可 ...

  4. ubuntu 下配置 开发环境

    1. apache: sudo apt-get install apache2 安装好输入网址测试所否成功: http://localhost 2. mongo 已经安装好了 版本:2.4.8 ref ...

  5. 《Linux内核分析》 第一节 计算机是如何工作的

    第一节 计算机是如何工作的 张嘉琪 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-100002900 ...

  6. Linux内核分析——第八周学习笔记

    实验作业:进程调度时机跟踪分析进程调度与进程切换的过程 20135313吴子怡.北京电子科技学院 [第一部分]理解Linux系统中进程调度的时机 1.Linux的调度程序是一个叫schedule()的 ...

  7. Mininet安装

    Mininet 安装 根据SDNLAB上的实验进行安装.连接地址 需要注意的是切换到用户目录下进行clone github上的源码. 1.卸载之前安装的Mininet 最好是先到目录下看是否有这些文件 ...

  8. 从苦逼到牛逼,详解Linux运维工程师的打怪升级之路

    做运维也快四年多了,就像游戏打怪升级,升级后知识体系和运维体系也相对变化挺大,学习了很多新的知识点. 运维工程师是从一个呆逼进化为苦逼再成长为牛逼的过程,前提在于你要能忍能干能拼,还要具有敏锐的嗅觉感 ...

  9. MT【183】借力打力

    (2011安徽省赛)设$f(x)=ax^3+bx+c(a,b,c\in R)$,当$0\le x \le1$时,$0\le f(x)\le1$,求$b$的可能的最大值. 分析:$f(0)=c,f(1) ...

  10. 【题解】 bzoj2115: [Wc2011] Xor (线性基+dfs)

    bzoj2115,戳我戳我 Solution: 看得题解(逃,我太菜了,想不出这种做法 那么丢个链接 Attention: 板子别写错了 又写错了这次 \(long long\)是左移63位,多了会溢 ...