Dubbo RPC源码解读
https://yq.aliyun.com/articles/272405#27
本文代码摘录的时候,将一些与本流程无关的内容去掉了,如有需要请看源码。
一、闲言碎语
使用rpc框架已经多年了,虽然之前有研究过rpc的过程,但是却不曾详细阅读过dubbo的源码,探究过其中的设计思路与亮点。所以抽时间阅读了一下dubbo的源码,分享出来和大家一起学习。
二、目标与示例
1. 目标
l? ? 探究dubbo rpc实现原理。
l? ? 探究rpc从发出请求到收到返回结果这整个过程的详细过程。
l? ? 学习rpc的负载均衡原理。
l? ? 学习服务暴露、服务发现的原理以及实现细节。
l? ? 多线程中dubbo是如何做到将返回结果和每个线程一一对应的。
本文重点为源码分析和模型实现分析,如果对dubbo的概念和使用不熟悉,情移步官网。
本文的所有分析均基于dubbo 2.5.3版本。
本文假定使用zookeeper管理服务。
2. 示例代码
以下的分析基于以下配置方式。不同的配置方式并不会影响本文所需要解决的几个问题,只是一下方式配置会比较便于理解,所以这里依次做为示例。
1) consumer
="rpcServiceRef" class="com.alibaba.dubbo.config.spring.ReferenceBean">
="interface" value="com.wzf.service.RpcService"/>
="application" ref="dubboApplicationConfig"/>
="registry" ref="dubboRegistryConfig"/>
="version" value="dev"/>
="timeout" value="3000"/>
="retries" value="0"/>
="check" value="false"/>
</bean>
2) provider
="rpcServiceExport" class="com.alibaba.dubbo.config.spring.ServiceBean">
="interface" value="com.wzf.funny.service.RpcService"/>
="ref" ref="rpcServiceImpl"/>
="application" ref="dubboApplicationConfig"/>
="registry" ref="dubboRegistryConfig"/>
="protocol" ref="dubboProtocolConfig"/>
="version" value="dev"/>
="timeout" value="0"/>
="retries" value="0"/>
</bean>
三、 模型
1. dubbo的模块模型

dubbo的模块模型有些复杂,不太容易看懂,如果你也有同感的话,可以看一下本文后面的几部分,他们详细讲述了dubbo中rpc的调用链,其中包括了核心的几个类,比较便于理解。
2. 服务调用关系模型

如图所示,dubbo的RPC调用模型分为registry、provider、consumer、monitor这几个部分。此图展示了从服务注册、发现、调用的全过程,但dubbo是如何做到的呢?其实这个问题包括了以下几个问题:provider如何注册服务到zookeeper;consumer如何从zookeeper拉取provider信息;provider变化以后,zookeeper如何告知consumer;consumer如何调用provider。另外,监控逻辑很简单本文暂时不做分析。
3. Provider
从源码上看ServiceBean主要完成以下几件工作:服务暴露,取消暴露服务。
1) 暴露服务
服务暴露开始于ServiceBean的afterPropertiesSet方法,此方法在ServiceBean的所有属性都被赋值以后被BeanFactory调用。服务暴露的调用链是: ServiceConfig#export -> ServiceConfig#doExport -> ServiceConfig#doExportUrls -> ServiceConfig#doExportUrlsFor1Protocol -> ServiceConfig#exportLocal(URL url)。 暴露服务其实包括两个类容:
l? ? 将Invoker存入AbstractProtocol#exporterMap,调用服务时从次map中取出Invoker直接使用。
protected final Set> invokers = new ConcurrentHashSet>();
其中key为:com.wzf.funny.service.ArticleService:dev, value为invoker对象
l? ? 将url注册到zookeeper。
此过程的入口在RegistryProtocol#export方法中,调用链为:
RegistryProtocol#export -> FailbackRegistry#register -> AbstractRegistry#register -> ZookeeperRegistry#doRegister -> ZookeeperClient#create -> AbstractZookeeperClient#create
2) 服务发现
ZookeeperRegistry是服务发现的核心类之一,实现了《服务调用关系模型》中的register、subscribe、notify。以下分析一下几个主要的方法。
l? 构造函数
从以下代码中可以看到,zkClient创建成功以后,会监听RECONNECTED事件,recover方法主要做一件事:将需要暴露的url放在failedRegistered(Set
public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
super(url);
//其他代码省略
this.root = group;
zkClient = zookeeperTransporter.connect(url);
zkClient.addStateListener(new StateListener() {
public void stateChanged(int state) {
if (state == RECONNECTED) {
try {
recover();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
}
});
}
创建zkclient的url示例如下:
zookeeper://localhost:2181/com.alibaba.dubbo.registry.RegistryService?application=funny&dubbo=2.5.3&interface=com.alibaba.dubbo.registry.RegistryService&pid=38796×tamp=1502594657663
l? register(URL url)
注册url代表的服务到zookeeper
l? unregister(URL url)
从zookeeper中删除之前注册的服务
l? subscribe(URL url, NotifyListener listener)
订阅url的服务
l? unsubscribe(URL url, NotifyListener listener)
取消订阅url对应的服务
l? notify(URL url, NotifyListener listener, List
通知
l? retry()
上面提到过,在recover()中将需要暴露的服务放到failedRegistered(Set
protected void retry() {
if (! failedRegistered.isEmpty()) {
for (URL url : failed) {
doRegister(url);
failedRegistered.remove(url);
}
}
if(! failedUnregistered.isEmpty()) {
for (URL url : failed) {
doUnregister(url);
failedUnregistered.remove(url);
}
}
if (! failedSubscribed.isEmpty()) {
for (Map.Entry, Set>> entry : failed.entrySet()) {
URL url = entry.getKey();
Set> listeners = entry.getValue();
for (NotifyListener listener : listeners) {
doSubscribe(url, listener);
listeners.remove(listener);
}
}
}
if (! failedUnsubscribed.isEmpty()) {
for (Map.Entry, Set>> entry : failed.entrySet()) {
URL url = entry.getKey();
Set> listeners = entry.getValue();
for (NotifyListener listener : listeners) {
doUnsubscribe(url, listener);
listeners.remove(listener);
}
}
}
if (! failedNotified.isEmpty()) {
for (Map, List>> values : failed.values()) {
for (Map.Entry, List>> entry:values.entrySet()) {
NotifyListener listener = entry.getKey();
List> urls = entry.getValue();
listener.notify(urls);
values.remove(listener)