title: dubbo Protocol实现剖析

date: 2018-09-09 19:10:07

tags:

2.6.3版本,之前读的是2.4.9版本

本篇主要阐述dubbo rpc的com.alibaba.dubbo.rpc.Protocol的实现,包括作用,用法,原理等等。

类与接口关系以及其装配

根据类与接口关系文档可以看到Protocol实现关系如下:

264 Protocol

--264.1 QosProtocolWrapper

--264.2 RegistryProtocol

--264.3 AbstractProtocol

----264.3.1 AbstractProxyProtocol

------264.3.1.1 HessianProtocol

------264.3.1.2 HttpProtocol

------264.3.1.3 RestProtocol

------264.3.1.4 RmiProtocol

------264.3.1.5 WebServiceProtocol

----264.3.2 DubboProtocol

----264.3.3 InjvmProtocol

----264.3.4 MemcachedProtocol

----264.3.5 RedisProtocol

----264.3.6 ThriftProtocol

----264.3.7 MockProtocol

--264.4 InjvmProtocol

--264.5 ProtocolFilterWrapper

--264.6 ProtocolListenerWrapper

默认会用到 QosProtocolWrapper RegistryProtocol(如果有注册中心) DubboProtocol InjvmProtocol ProtocolFilterWrapper ProtocolListenerWrapper。

在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

扩展点机制createAdaptiveExtensionClass

要看懂protocol的实例化与export过程最好先了解下扩展点机制,前面有文章写过。 此处要需要注意ExtensionLoader类的下面代码:

private Class<?> createAdaptiveExtensionClass() {
String code = createAdaptiveExtensionClassCode();
ClassLoader classLoader = findClassLoader();
com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
}

再协议初始化与export时会动态生成的两个类的代码: Protocol$Adaptive,ProxyFactory$Adaptive,看上面code变量就是生成的具体代码了。

protocol及其wrap之间的关系

在扩展点机制com.alibaba.dubbo.common.extension.ExtensionLoader.createExtension(String)中:

在创建扩展实例时,会同时对他的wrap类的实例进行创建,代码细节如下:

T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
injectExtension(instance);// 此时是DubboProtocol示例
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));// 此处创建wrap实例
}
}

实际应用中,以dubbo协议这个扩展为例,当创上述代码建了DubboProtocol协议的instance,此时是DubboProtocol实例,接下来对cachedWrapperClasses进行for循环,创建对应的wrap类,这些wrap类都有一个以Protocol类型的参数,那么创建wrap实例的时候,会把之前产生的Protocol实例传递给他。

此例,cachedWrapperClasses里的数据来自于META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol文件中配置的是wrap的扩展,

此处是:

class com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper

class com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper,

class com.alibaba.dubbo.qos.protocol.QosProtocolWrapper,

(怎么从配置文件中识别是否是wrap类?答案是 只要这个QosProtocolWrapper类有类型为Protocol的构造函数存在,就当QosProtocolWrapper是wrap类。)

那么此处便会依次构造这三个类。 这个三个类的构造顺序不固定,因为用的上面createExtension代码中cachedWrapperClasses是ConcurrentHashSet维护的。

export机制

整体构造与export顺序即触发时机的解释

此处是以provider端为例

DubboProtocol.() line: 162 // final int defaultPort = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(name).getDefaultPort(); ServiceConfig取默认端口值 ,用在没有对dubbo:protocol标签配置端口时

ProtocolListenerWrapper.(Protocol) line: 41 // 上面实例化DubboProtocol实例时,会级联实例化其关联的wrap类,级联的wrap类是怎么获取的 参见级联的wrap类是怎么获取部分

QosProtocolWrapper.(Protocol) line: 39 // 上面实例化DubboProtocol实例时,会级联实例化其关联的wrap类

ProtocolFilterWrapper.(Protocol) line: 40 // 上面实例化DubboProtocol实例时,会级联实例化其关联的wrap类

ProtocolListenerWrapper.(Protocol) line: 41 // ServiceConfig 对本端需要暴露的服务 做exportLocal(url) 时,会export injvm协议,那么这时又会级联实例化 其关联的wrap类,同样是3个 :

QosProtocolWrapper.(Protocol) line: 39 // ServiceConfig 做exportLocal(url) 时,会export injvm协议,级联实例化,同上

ProtocolFilterWrapper.(Protocol) line: 40 // ServiceConfig 做exportLocal(url) 时,会export injvm协议,级联实例化,同上

ProtocolFilterWrapper.export(Invoker) line: 97 // 对上面做exportLocal时级联创建出来的wrap类调用export方法 ,而这三个wrap类又会对他内部的protocol做export调用 就是下面这几个

QosProtocolWrapper.export(Invoker) line: 52 // 被上行这个级联export的

ProtocolListenerWrapper.export(Invoker) line: 54 // 被上行这个级联export的

InjvmProtocol.export(Invoker) line: 86 // 被上行这个级联export的 结果是创建一个InjvmExporter实例, 也即是说export可能是调自己级联的protocol,也可能是创建一个exporter

RegistryProtocol.() line: 73

//

Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); Exporter<?> exporter = protocol.export(wrapperInvoker);

针对 registryURL 先创建invoker,再用这个invoker创建wrapperInvoker,再用wrapperInvoker做exporter。

ProtocolListenerWrapper.(Protocol) line: 41 // 被RegistryProtocol级联创建

QosProtocolWrapper.(Protocol) line: 39 // 被RegistryProtocol级联创建

ProtocolFilterWrapper.(Protocol) line: 40 // 被RegistryProtocol级联创建

ProtocolFilterWrapper.export(Invoker) line: 97 // RegistryProtocol做export时先级联的wrap实例进行export 这三个wrap中 对REGISTRY_PROTOCOL做了特殊逻辑处理。 详细请参见代码

QosProtocolWrapper.export(Invoker) line: 52 // 同上

ProtocolListenerWrapper.export(Invoker) line: 54 // 同上

RegistryProtocol.export(Invoker) line: 132 // 同上 最终export出了DestroyableExporter 就是对应Registry协议的exporter

ProtocolFilterWrapper.export(Invoker) line: 97 // 上行RegistryProtocol export时先做doLocalExport,doLocalExport时把provider的url与其对应的exporter做一个缓存(如果缓存中没有,此时要export,并把对provider export出来的结果做ExporterChangeableWrapper的包装,最后返回ExporterChangeableWrapper实例。这行的这个export就是被这个触发的,详细的可以参见他代码)

String key = getCacheKey(originInvoker);
ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
if (exporter == null) {
synchronized (bounds) {
exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
if (exporter == null) {
final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));
exporter = new ExporterChangeableWrapper<T>((Exporter<T>) protocol.export(invokerDelegete), originInvoker);
bounds.put(key, exporter);
}
}
}

QosProtocolWrapper.export(Invoker) line: 52 // 上一行级联触发

ProtocolListenerWrapper.export(Invoker) line: 54 // 上上一行级联触发

DubboProtocol.export(Invoker) line: 229 // 上上上一行级联触发

ProtocolFilterWrapper

rpc过滤器链的组织创建者。

如果是注册中心 级联export的,直接级联wrap的protocol进行export,不做其他处理。

如果是非注册中心 级联export的,比如injvm dubbo这种协议级联的,做buildInvokerChain 构建调用中过滤器链的处理。 就是前面博客介绍的rpc过滤器com.alibaba.dubbo.rpc.Filter的实现实例串起来。通过循环加内部类访问外围变量的方式组成的链。贴下关键代码:

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 Result invoke(Invocation invocation) throws RpcException {
return filter.invoke(next, invocation);
}
// ......
};
}

next与last配合使用形成了链。

QosProtocolWrapper

如果是非注册中心 级联export的,直接级联wrap的protocol进行export,不做其他处理

如果是注册中心 级联export的,启动QoS server。参见com.alibaba.dubbo.qos.server.Server。

ProtocolListenerWrapper

用于服务export时候监听机制的插入。

如果是注册中心 级联export的,直接级联wrap的protocol进行export,不做其他处理。

如果是非注册中心 级联export的,比如injvm dubbo这种协议级联的,先级联wrap的protocol进行export,再创建ListenerExporterWrapper实例。 主要是为了让ExporterListener监听器插入进来。

RegistryProtocol

对应注册中心协议

先做本地的export

然后根据配置将 registry://127.0.0.1:2174/com.alibaba.dubbo.registry.RegistryService?application=hello-...置换成具体的注册中心协议地址,如: zookeeper://127.0.0.1:2174/com.alibaba.dubbo.registry.RegistryService?application=hello-world-...

根据注册中心协议地址 或者Registry-->ZookeeperRegistry的实例。

判断是否是非延迟暴露的,就开始注册,比较简单:

zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true));

可以通过其父类FailbackRegistry看到,支持失败重试,由一个定时调度线程重试。

this.retryFuture = retryExecutor.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
// Check and connect to the registry
try {
retry();
} catch (Throwable t) { // Defensive fault tolerance
logger.error("Unexpected error occur at failed retry, cause: " + t.getMessage(), t);
}
}
}, retryPeriod, retryPeriod, TimeUnit.MILLISECONDS);

Failback是一种容错,详细的可以参见 常见容错机制。

注册之后是对OverrideListener 的处理。

该export返回的是DestroyableExporter实例,顾名思义是可以销毁的exoprter,在其unexport里做了文章,移除注册,移除export。

ref机制

先看下consumer端发起调用时的链路流程:

 +---------------------------+            +---------------------------+            +---------------------------+
| helloService | | proxy | | InvokerInvocationHandler |
| sayHello +----------> | sayHello +----------> | invoke |
| | | | | proxy method args |
+---------------------------+ +---------------------------+ +-------------+-------------+
|
|
+---------------------------------+
| | |
| +------------v--------------+ |
| | MockClusterInvoker | |
| | invoke | |
| | | |
| +------------+--------------+ |
| | |
| | |
| | |
+---------------------------+ +---------------------------+ | +------------v--------------+ |
| Router | | RegistryDirectory | | | FailoverClusterInvoker | |
| route | <----------+ list | <-----------+ invoke | |
| MockInVokersSelector | | INVOCATION-->List INVOKER | | | | |
+------------+--------------+ +---------------------------+ | +---------------------------+ |
| | |
| +---------------------------------+
| cluster invoke,分布式调用容错机制也是在这做
|
|
|
|
|
+-------------v-------------+ +---------------------------+ +---------------------------+
| RandomLoadBalance | |InvokerDelegate | | ListenerInvokerWrap |
| select +-----------> |invoke +-----------> | invoke |
| List INVOKER-->INVOKER | | | | |
+---------------------------+ +---------------------------+ +---------------------------+

RegistryProtocol 注册中心协议 对接口协议接口约束,不论何种注册中心都走这个实现。

RegistryDirectory 提供由服务目录查找出真正能提供服务的所有提供方

Registry 各个注册中心的实现的接口约束,具体实现有 RedisRegistry ZookeeperRegistry MulticastRegistry DubboRegistry。 主要完成注册这个逻辑。 就是把自己的信息写入注册中心。

Cluster 多服务提供方的一个封装 。他们负责创建多服务调用者实例。 比如FailoverCluster负责创建FailoverClusterInvoker实例。Cluster的实现是以分布式容错机制Failover,Failfast,Failback等这些维度进行。 对应invoker也是按照这个维度划分,比如FailbackClusterInvoker,FailfastClusterInvoker,BroadcastClusterInvoker等等。

Router 路由机制

LoadBalance 负载均衡机制

Invoker 调用者。 按分布式容错机制的维度进行划分了多个实现,与 Cluster实现一一对应。

基本上描述了 由consumer的ref类-->proxy-->InvokerInvocationHandler-->clusterinvoker-->服务目录查找-->路由机制-->负载均衡机制-->调用委托-->调用监听器。

Invoker的实现结构:

--61.1 Invoker

----61.1.1 DelegateProviderMetaDataInvoker

----61.1.2 ConsumerInvokerWrapper

----61.1.3 ProviderInvokerWrapper

----61.1.4 AbstractClusterInvoker

------61.1.4.1 AvailableClusterInvoker

------61.1.4.2 BroadcastClusterInvoker

------61.1.4.3 FailbackClusterInvoker

------61.1.4.4 FailfastClusterInvoker

------61.1.4.5 FailoverClusterInvoker

------61.1.4.6 FailsafeClusterInvoker

------61.1.4.7 ForkingClusterInvoker

----61.1.5 MergeableClusterInvoker

----61.1.6 MockClusterInvoker

----61.1.7 ListenerInvokerWrapper

----61.1.8 AbstractInvoker

------61.1.8.1 ChannelWrappedInvoker

------61.1.8.2 DubboInvoker

------61.1.8.3 InjvmInvoker

------61.1.8.4 ThriftInvoker

----61.1.9 InvokerWrapper

----61.1.10 AbstractProxyInvoker

----61.1.11 DelegateInvoker

----61.1.12 MockInvoker

RegistryProtocol

ref完成的逻辑是:

创建服务目录实例 RegistryDirectory

创建ClusterInvoker,默认是FailoverClusterInvoker..

其他

ProtocolFilterWrapper, QosProtocolWrapper, ProtocolListenerWrapper的ref逻辑与export基本相同,不再赘述。

dubbo Protocol实现剖析的更多相关文章

  1. Dubbo源码剖析三之服务注册过程分析

    Dubbo源码剖析二之注册中心 - 池塘里洗澡的鸭子 - 博客园 (cnblogs.com)中对注册中心进行了简单的介绍,对Dubbo整合Zookeeper链接源码进行了详细分析.本文接着对服务注册过 ...

  2. 【DUBBO】Dubbo:protocol 的配置项

    [一]:配置项 <dubbo:protocol id="标识" port="端口号" name="名字"/> [二]:配置解析器 ...

  3. Dubbo源码剖析六之SPI扩展点的实现之Adaptive功能实现原理

    接Dubbo源码剖析六之SPI扩展点的实现之getExtensionLoader - 池塘里洗澡的鸭子 - 博客园 (cnblogs.com)继续分析Adaptive功能实现原理.Adaptive的主 ...

  4. Dubbo源码剖析六之SPI扩展点的实现之getExtension

    上文Dubbo源码剖析六之SPI扩展点的实现之getExtensionLoader - 池塘里洗澡的鸭子 - 博客园 (cnblogs.com)中分析了getExtensionLoader,本文继续分 ...

  5. Dubbo源码剖析一之整体架构设计

    Dubbo基础二之架构及处理流程概述 - 池塘里洗澡的鸭子 - 博客园 (cnblogs.com)中进行Dubbo职能上的简单介绍,下面就其内部进行详细探究: 1.Dubbo调用关系 这个图是不是很熟 ...

  6. Dubbo源码剖析五之服务本地缓存

    Dubbo调用者需要通过注册中心(例如:ZK)注册信息,获取提供者.但是如果频繁从ZK获取信息肯定会存在单点故障问题,所以Dubbo提供了将提供者信息缓存在本地的方法. Dubbo在订阅注册中心的回调 ...

  7. Dubbo源码剖析六之SPI扩展点的实现之getExtensionLoader

    Dubbo SPI机制之三Adaptive自适应功能 - 池塘里洗澡的鸭子 - 博客园 (cnblogs.com)中,示例案例中自定义了扩展接口而不是使用Dubbo已提供的扩展接口.在案例中,主程序分 ...

  8. Dubbo源码剖析二之注册中心

    Dubbo基础二之架构及处理流程概述 - 池塘里洗澡的鸭子 - 博客园 (cnblogs.com)中架构中,无论是服务提供者还是服务消费者都离不开注册中心,可见注册中心之重要.Redis.Nacos. ...

  9. Dubbo剖析-SPI机制

    文章要点: 1.什么是SPi 2.Dubbo为什么要实现自己的SPi 3.Dubbo的IOC和AOP 4.Dubbo的Adaptive机制 5.Dubbo动态编译机制 6.Dubbo与Spring的融 ...

随机推荐

  1. 通过Maven构建打包Spring boot,并将config配置文件提取到jar文件外

    如果通过不同的IDE打包,着实会觉得依赖性太大,并且容易出现错误,操作也比较复杂 同时,spring-boot-maven-plugin的使用感觉,相关配置太少,并且无法满足方便部署和运行的需求. 这 ...

  2. vue2.0:(八)、外卖App弹窗部分知识点总结

    本篇文章是对外卖App弹窗部分知识点的总结. 知识点一:如何从接口取出不同的图片. 答: 1.header.vue: 代码: <ul v-if="seller.supports&quo ...

  3. 使用as开发jni入门(附验证):配置ndk开发环境,配置as相关jni配置

    编写jni,生成so文件: 1.通过as内置的Android SDK下载需要使用的ndk,在系统环境变量设置相关参数 2.新建一个普通as项目,新建一个类,用来静态加载so库和书写本地native方法 ...

  4. URAL 1057 Amount of Degrees (数位DP,入门)

    题意: 求给定区间[X,Y]中满足下列条件的整数个数:这个数恰好等于K个互不相等的,B的整数次幂之和.例如,设X=15,Y=20,K=2,B=2,则有且仅有下列三个数满足了要求:  17 = 24+2 ...

  5. 洛谷 P1345 [USACO5.4]奶牛的电信Telecowmunication

    题目描述 农夫约翰的奶牛们喜欢通过电邮保持联系,于是她们建立了一个奶牛电脑网络,以便互相交流.这些机器用如下的方式发送电邮:如果存在一个由c台电脑组成的序列a1,a2,...,a(c),且a1与a2相 ...

  6. [学习总结] python语言学习总结 (二)

    1.python中的拆包 之前就只写了*可以是未知数量的参数,**可以传入未知数量命名参数.这次详细记下拆包. def f1(a, *l): print(a) # 不拆包 print(l) # 拆包 ...

  7. Mysql command line

    show databasename; use databasename; show tables; desc tablename;

  8. 使用max函数计算EXCEL个税公式

    1.Max()函数是求括号内的数的最大值.2.其中,第一和第二个大括号{}内的数,相信作为财务的应该很清楚,就是个人所得税的缴税比例,以及速算个人应缴所得税的相关数据.3.在EXCEL中,使用{}表示 ...

  9. NOIP模拟赛 无线通讯网

    [题目描述] 国防部计划用无线网络连接若干个边防哨所.2种不同的通讯技术用来搭建无线网络:每个边防哨所都要配备无线电收发器:有一些哨所还可以增配卫星电话. 任意两个配备了一条卫星电话线路的哨所(两边都 ...

  10. 使用apache benchmark(ab) 测试报错: apr_socket_recv: Connection timed out (110)

    使用ab( apache benchmark )测试的时候,使用如下命令: ab -n 15000 -c 200   http://localhost/abc/abc.php 执行操作一定条数,或连续 ...