zoukankan      html  css  js  c++  java
  • dubbo源码解析一

      最近研究dubbo,准备把dubbo翻个底朝天。

          dubbo框架的介绍,官网的开发文档已经很详细了,不再介绍。文档也有源码导读,覆盖了大部分关键模块,大致看了一遍,对dubbo对理解加深了不少,不过中间也有不少疑问。个人认为,dubbo的开发文档非常详尽,从使用、设计、源码方面都使开发者对dubbo的认识变得简单。但是而我写源码解析的原因,是因为官方文档的局限,毕竟受众太多,内容全而固定,如果不是非常严肃的研究框架,有种想睡着的意思。本文从dubbo使用者的角度,带着问题过一遍dubbo的整体流程,进一步加深理解并留下文字备忘。

          一般人看代码习惯先找入口,我也是,比如一般java程序的main函数。我们先找找dubbo的入口,dubbo的使用,一般以下几个步骤:

    1、开发provider接口及实现类,spirng的xml配置dubbo bean,内容有注册中心、端口、接口bean等;

    2、comsumer同上

    3、comsumer端引入provider端的接口定义

    4、comsumer端注入provider端bean,像本地一样调用接口

    以下是一个comsumer的demo,stockService是dubbo远程接口

    public static void main(String args[]) throws Exception {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
    new String[] { "dubbo-client.xml" });
    final StockService stockService = (StockService) context.getBean("StockService");
    int previousAmount = 0;
    previousAmount = stockService.getSum().intValue();
    System.out.println("previousAmount=" + previousAmount);
    System.exit(0);
    }
    我们就从标红的代码开始看起,StockService是comsumer引入provider端的一个接口,既然可以在这可以直接使用,必然是dubbo为之生成了一个代理。
    在标红处打断点,可以看出StockService的代理类,如下

     这里能看出,StockService的代理类是由jdk动态代理生成,执行的invoke方法是传入的InvokerInvocationHandler实例的invoke方法。

    继续debug

    InvokerInvocationHandler实例构造方法,传入一个invoker,最后调用invoker.invoke。Invoker是dubbo里的实体域,任何执行体都往invoker上靠,详细信息见dubbo开发文档,在这里,简单的说就是一个封装里远程调用的代理对象。继续走,对InvokerInvocationHandler右键find usages,只有两个地方使用

    两个代理工厂,javassist 和 jdk 代理,看代码可以看出,先根据拿到的接口,生成代理类,并实例化。

    两种方式功能一样,dubbo默认使用javassist,对生成代理的几种方式,dubbo作者的博客可以参考下。

    代理类反编译后格式如下

    以上代码来自开发文档,并不是我的demo,能理解即可。可以看到,和jdk的动态代理生成的代理类非常相似,实现接口的方法,直接调用来invocationHandler的invoke方法。

    看到这里,冒出了几个问题。

    1、invoker在dubbo作为执行体,什么时候初始化并加载的?

    2、第一层invoker为什么是mock?invoke链的结构和内容怎么生成的?

    第一个问题不难想到,dubbo依赖spring框架,bean的生命周期必然被spring的管理使用远程服务接口,得先引入服务,接下来参照开发文档,分析引入服务的过程

    服务引用过程

    我们追踪源码的顺序是 调用服务->发现一些内容已经加载(如invoker)-> 追踪内容如何初始化->继续看服务如何调用。

    在源码追踪过程中,我们约定默认大部分配置为缺省,并跳过安全检查、分支逻辑。

     以上来自官方文档。

    ReferenceBean对应了dubbo配置的reference标签,看下demo里的配置

    <dubbo:reference id="StockService" interface="com.tuya.txc.dubbo.StockService"/>

    这个配置经过dubbo的解析器解析后,就是referenceBean,因为referenceBean需要返回stockService的一个代理类,所以得实现FactoryBean接口。

    FactoryBean是spring的一个扩展点,被其他bean注入的时候,并不是返回当前类的实例,而是调用覆盖接口的getObject方法,返回一个自定义的类对象。

    就开始的demo来说,当执行 final StockService stockService = (StockService) context.getBean("stockService"); 的时候,即注入stockService的时候,会调用id=stockService的bean的getObject方法。这也就是开发文档提到的懒汉式引用。

    接下来我们关注referenceBean.getObject方法,源码不贴了,详细流程在开发文档上有,查看链接

    主要工作是

    1、导入其他dubbo配置到bean中,如果没有尝试从系统变量或者其他路径导入

    2、createProxy,传入的map参数,保存了其他dubbo配置即本地ip等信息

    createProxy方法包含内容(按demo配置)

    1、加载注册中心  List<URL> us = loadRegistries(false);

    2、调用Protocol的代理类refer方法 构建 Invoker 实例 invoker = Protocol$Adpative.refer(interfaceClass, urls.get(0));

    3、生成代理类 return (T) proxyFactory.getProxy(invoker);

     loadRegistries 读取配置

    <dubbo:registry address="127.0.0.1:2181" protocol="zookeeper" timeout="300000"/>

    封装成URL,格式如下

    作为参数传入Protocol$Adpative的refer方法。

    Protocol$Adpative是Protocol的动态代理类,作为一个静态属性,在serverConfig由spi的自适应语句生成。

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

    protocol代理类默认由javaassist生成,生成的class的refer内容如下:

    public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1)
                throws java.lang.Class {
            if (arg1 == null)
                throw new IllegalArgumentException("url == null");
            com.alibaba.dubbo.common.URL url = arg1;
            String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
            if (extName == null)
                throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url("
                        + url.toString() + ") use keys([protocol])");
            com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader
                    .getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
            return extension.refer(arg0, arg1);
        }
    这里url的协议头是registry,所以extName=registry传入 getExtension方法,返回类Protocol实例extension,接下来我们看下返回的extension是什么。

    这里重点在createExtension 方法

    createExtension 方法的逻辑稍复杂一下,包含了如下的步骤:

    1. 通过 getExtensionClasses 获取所有的拓展类
    2. 通过反射创建拓展对象
    3. 向拓展对象中注入依赖
    4. 将拓展对象包裹在相应的 Wrapper 对象
    
    
    以上步骤中,第一个步骤是,依据是传入的Protocol接口和registry关键字加载RegistryProtocol类,第二步反射生成RegistryProtocol对象,第三步骤遍历set方法,从bean工厂找到依赖并注入RegistryProtocol对象。
    第四个步骤,则会找到所有Protocol的wrapper类,对RegistryProtocol对象层层包裹,最后返回。

    什么是Wrapper类

    那什么样类的才是Dubbo扩展机制中的Wrapper类呢?Wrapper类是一个有复制构造函数的类,也是典型的装饰者模式。下面就是一个Wrapper类:

    class A{
        private A a;
        public A(A a){
            this.a = a;
        }
    }

    类A有一个构造函数public A(A a),构造函数的参数是A本身。这样的类就可以成为Dubbo扩展机制中的一个Wrapper类。

    怎么配置Wrapper类

    在Dubbo中Wrapper类也是一个扩展点,和其他的扩展点一样,也是在META-INF文件夹中配置的。

    比如前面举例的ProtocolFilterWrapper和ProtocolListenerWrapper就是在路径dubbo-rpc/dubbo-rpc-api/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol中配置的:

    filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
    listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
    mock=com.alibaba.dubbo.rpc.support.MockProtocol
    dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
    injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol
    rmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocol
    hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
    com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
    com.alibaba.dubbo.rpc.protocol.webservice.WebServiceProtocol
    thrift=com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocol
    memcached=com.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocol
    redis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol
    rest=com.alibaba.dubbo.rpc.protocol.rest.RestProtocol
    registry=com.alibaba.dubbo.registry.integration.RegistryProtocol
    qos=com.alibaba.dubbo.qos.protocol.QosProtocolWrapper

    在Dubbo加载扩展配置文件时,有一段如下的代码:

    这段代码的意思是,如果扩展类有复制构造函数,就把该类存起来,供以后使用。有复制构造函数的类就是Wrapper类。通过clazz.getConstructor(type)来获取参数是扩展点接口的构造函数。注意构造函数的参数类型是扩展点接口,而不是扩展类。
    以Protocol为例。配置文件dubbo-rpc/dubbo-rpc-api/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol中定义了filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper

    ProtocolFilterWrapper代码如下:

    ProtocolFilterWrapper有一个构造函数public ProtocolFilterWrapper(Protocol protocol),参数是扩展点Protocol,所以它是一个Dubbo扩展机制中的Wrapper类。ExtensionLoader会把它缓存起来,供以后创建Extension实例的时候,使用这些包装类依次包装原始扩展点。

    回到createExtension的第四点,dubbo从META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol配置文件中,挨个找到Protocol的wrapper类,把RegistryProtocol对象层层包裹后,返回给ServerConfig对象,最后调用wrapper类的refer方法返回invoker。

    Protocol的配置文件有3个wrapper类,按顺序排列如下。

    filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
    listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
    qos=com.alibaba.dubbo.qos.protocol.QosProtocolWrapper

    层层包裹后,refer的调用路径相反,Protocol$Adpative->QosProtocolWrapper->ProtocolListenerWrapper->ProtocolFilterWrapper->RegistryProtocol

    qos开启dubbo命令行功能,ProtocolListenerWrapper对几个关键方法注册监听回调方法,ProtocolFilterWrapper对默认filter和自定义filter分装成链,依次执行,最后执行RegistryProtocol的refer方法。

    
    

    1、初始化服务目录Directory类

    2、Directory订阅zk的dubbo临时节点更新

    3、cluster获取invoker

    Directory可以看做是 Invoker 集合,且这个集合中的元素会随注册中心的变化而进行动态调整。Provider和Consumer向Zookeeper注册临时节点,当连接断开时删除相应的注册节点。

    Consumer订阅Providers节点的子节点,通过zkclient的通知api实时感知Provider的变化情况,实时同步自身的Invoker对象,保证RPC的可用性。这里的cluster是前面RegistryProtocol的createExtension方法的injectInstanse时注入的自适应cluster对象,和protocol对象一样,先对cluster接口生成动态代理类,在接口方法join里,先实例化@SPI标签的name的FailoverCluster对象,再从配置文件中遍历cluster的wrapper类进行封装,最后执行wrapper类的join方法。

    Invoker invoker = cluster.join(directory); 
    完整调用路径:Cluster$Adpative->MockClusterWrapper->FailoverCluster.join

    按照这个调用路径,最后返回了包含FailoverClusterInvoker对象的MockClusterInvoker实例,即文章开头debug进入的第一个invoker

    继续回到ReferenceConfig代码,在返回了invoke后还有最后一步 proxyFactory.getProxy(invoker) 生成代理类,这个并不复杂,不再展开讲述。

    总结:本文从使用者第一视角跟踪调用远程服务的过程,及过程中代理类,invoker链如何在服务引入的时候初始化。下节继续跟踪invoker链

     

  • 相关阅读:
    Linux 间网线直连
    Ubuntu 14.04安装配置NFS
    Android Native IPC 方案支持情况
    使用WindowsAPI获取录音音频
    Ubuntu 64编译32位程序
    TensorFlow 安装 Ubuntu14.04
    纯css实现表单输入验证
    安装ELectron失败解决方案
    正则学习
    常见web攻击
  • 原文地址:https://www.cnblogs.com/wxbty/p/10345148.html
Copyright © 2011-2022 走看看