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链

 

posted @ 2019-02-01 11:11  妄言12345  阅读(342)  评论(0编辑  收藏  举报