在前东家一直从事偏客户端相关的工作,进了新东家之后终于有机会从事大型的分布式系统开发,在微服务大行其道的今天,dubbo框架作为一款优秀的开源RPC框架使用非常广泛,在使用dubbo的这几个月里,用零碎的时间对dubbo框架的部分源码浅读了一番,平时工作较忙,历时几个月才把读代码的过程整理出来,现在把这个过程记录下来,希望对跟我一样的初学者小伙伴有所帮助。分五个部分逐步对dubbo服务调用流程进行简单的剖析:
1.插件机制
2.标签解析
3.提供者初始化
4.消费者初始化
5.远程服务调用流程。

dubbo插件机制

Dubbo除了提供了高效的远程调用,其灵活的扩展机制也是值得我们学习的,提供了丰富的扩展点和灵活性,框架内部核心组件都采用插件化设计,使用者可以任性的替换、扩展。框架的内部实现也依托它的扩展点框架,所以在研读dubbo源码之前先要了解它的插件化机制以及实现,要不然在读框架的代码的时候容易晕头转向。
可以通过在classpath的META-INF/dubbo/internal/、META-INF/dubbo/、META-INF/services/目录下放置文件来定义扩展点,文件名称为组件接口的类全名,文件内容为扩展名=实现类名的形式,例如Protocol的扩展点文件(文件名称com.alibaba.dubbo.rpc.Protocol):

registry=com.alibaba.dubbo.registry.integration.RegistryProtocol
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=com.alibaba.dubbo.rpc.support.MockProtocol
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=memcom.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocol
redis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol

框架通过ExtensionLoader负责加载/存储扩展点实例,每个组件接口有一个对应的ExtensionLoader实例负责加载该组件的扩展点,有三种方式加载:
1、名称加载扩展点
2、加载激活扩展点
3、加载自适应扩展点

名称加载扩展点

对应ExtensionLoader.getExtension方法
这是最直观的方式,在初始化时框架会读取classpath下所有扩展点文件形成多个键值对存在在扩展点对应的ExtensionLoader中,比如要获取名称为dubbo的Protocol扩展点,那么就会返回一个DubboProtocol实例,但是框架还有种特殊的扩展点叫Wrapper扩展点,即实现类有一个参数并且参数类型是扩展点接口类型的构造函数的扩展点,这类扩展点一般做一些适配和封装性的工作。当有Wrapper扩展点时,获取任何名称的扩展点都会返回Wrapper实例,Wrapper实例封装真实的扩展实现,当有多个Wrapper实现时,会Wrapper封装Wrapper,形成一个链式的结构,比如获取名称是dubbo的Protocol扩展点,通过ExtensionLoader.getExtension(“dubbo”)获取,ExtensionLoader会返回一个ProtocolListenerWrapper实例,ProtocolListenerWrapper实例持有一个ProtocolFilterWrapper实例,ProtocolListenerWrapper实例持有持有一个DubboProtocol实例,对象引用关系:ProtocolListenerWrapper->ProtocolFilterWrapper->DubboProtocol。Wrapper扩展点相关的代码见下面代码片段:

com.alibaba.dubbo.common.extension.ExtensionLoader.loadFile(Map<String, Class<?>>, String)

代码

代码

加载激活扩展点

对应ExtensionLoader.getActivateExtension方法
在应用进行dubbo调用时,dubbo框架会根据条件加载一些自动激活的扩展点,最典型就是Filter组件,dubbo接口调用时会激活应用配置和框架内置的Filter调用链。自动激活的扩展点一般通过两种形式定义:
1. 通过Activate注解,框架启动时ExtensionLoader会加载所有带Activate注解的接口实现,并存在一个Map中,key是扩展点实现的类名首字母小写,如果实现类的结尾是接口名称,截掉这个接口名称,比如MonitorFilter实现,处理之后key就是monitor。

com.alibaba.dubbo.common.extension.ExtensionLoader.loadFile(Map<String, Class<?>>, String)

代码

在查找被激活的扩展点时,如果Activate注解设置了group和values属性,要根据这两个属性进行过滤,group属性有两个枚举值:provider、consumer,设置了group=”provider”则调用时提供端会加载此扩展点,设置了group=”consumer”则调用时消费端会加载此扩展点,如果设置了group={”provider”, ”consumer”}则提供端和消费端都会加载此扩展点,当然还要有个前提条件,就是应用层没有禁用次扩展点,禁用的方式就是在配置时扩展点名称前面加个-号,比如设置了filter=”-monitor”,那么不会激活MonitorFilter这个扩展点。如果values属性设置了值,那么框架会检查封装此次调用所有信息的URL的parameters中是否在values中的key,如果有的话也激活该扩展点。
1. 应用中显示设置的扩展点,如果Filter,那么使用这可以通过给接口设置filter属性来达到激活扩展点的目的。

com.alibaba.dubbo.common.extension.ExtensionLoader.getActivateExtension(URL, String[], String)
代码

加载自适应扩展点

对应ExtensionLoader.getAdaptiveExtension方法
每一次dubbo接口调用都是框架中的多个不同的组件来合作完成的,这时候完成核心工作的组件也是通过ExtensionLoader来加载的,加载的扩展点由调用上下文决定,这种扩展点加载方式叫自适应加载。Dubbo框架通过Adaptive注解来定义自适应扩展点,同一个扩展点接口的实现中最多只能有一个实现类可以定义Adaptive注解,定义了Adaptive注解的扩展点就是被加载到的自适应扩展点Map中,如果所有实现都没有使用Adaptive注解,那么接口需要使用SPI注解,并且设置value属性,value属性值就是默认扩展点的名称,如果URL中未指定加载哪个扩展点则加载默认扩展点。同时需要扩展的方法也打上Adaptive注解。自适应扩展点加载逻辑是:在URL中获取扩展点名称,这个名称一般放在Url的某个属性或者parameters的某个key中,加入URL中没有取到响应的名称取默认值,就是SPI注解中指定的value属性。每个组件的自适应扩展点的加载都是通过该接口的一个代理实现来完成,框架为了消除重复代码,该代理类并没有对应的静态代码,代码是dubbo框架通过一个代码模板在运行时生成的,然后通过Compiler组件把生成的代码编译并加载成代理类Class(相关代码见com.alibaba.dubbo.common.extension.ExtensionLoader.createAdaptiveExtensionClassCode()这个方法),例如,当要加载Cluster自适应组件时,dubbo框架会在生成下面这段代码,然后把它当做Cluster组件的自适应加载代理类:


package com.alibaba.dubbo.rpc.cluster;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Cluster$Adpative implements com.alibaba.dubbo.rpc.cluster.Cluster {
public com.alibaba.dubbo.rpc.Invoker join(com.alibaba.dubbo.rpc.cluster.Directory arg0) throws com.alibaba.dubbo.rpc.cluster.Directory {
if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.cluster.Directory argument == null");
if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.cluster.Directory argument getUrl() == null");com.alibaba.dubbo.common.URL url = arg0.getUrl();
String extName = url.getParameter("cluster", "failover");
if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.cluster.Cluster) name from url(" + url.toString() + ") use keys([cluster])");
com.alibaba.dubbo.rpc.cluster.Cluster extension = (com.alibaba.dubbo.rpc.cluster.Cluster)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.cluster.Cluster.class).getExtension(extName);
return extension.join(arg0);
}
}

从上面的代码也可以看出,扩展点名称从URL中获取,加入URL中没有指定对应的名称,则去默认扩展实现,Cluster组件的SPI注解value=failover,所以上面代码的extName默认值是failover。

com.alibaba.dubbo.rpc.cluster.Cluster
代码

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐