目录导航
前言
欢迎关注本套Java系列文章,导航地址:Java架构师成长之路
关于Dubbo,本系列文章主要讲三方面内容。前两讲我们已经了解到Dubbo的基本特性,常用配置以及简单了解了一下Dubbo自适应扩展点源码的内容。这一节,就让我们深入走进Dubbo的源码世界的核心,一探究竟吧~
- 解开Dubbo的神秘面纱
- Dubbo常用配置
- Dubbo源码分析(上篇)
- Dubbo源码分析(中篇)
- Dubbo源码分析(下篇)
关于Dubbo 的源码,我想从以下几个方面来介绍:
- Dubbo Extension扩展点
- 服务端的服务发布与注册过程
- 消费端初始化过程
- 服务端调用过程
- Directory
- Cluster
- LoadBalance
在Dubbo源码分析这一部分,由于篇幅过长,我这里分上下两节来解析这7个方面内容
那么本节作为dubbo源码的第一篇,我们就来说一下服务发布的过程吧~
(注:1. Dubbo Extension扩展点 在上一小节Dubbo常用配置已讲,这里做一个复习)
Tips:本节文末有Dubbo中文注释版的源码哦~
首先,我们从官方下载dubbo的源码到本地,然后我们会发现它的项目结构如下(目前我使用的是3.5.4版本):
学习源码,第一点就是了解整体架构,源码结构,然后再找入口,逐层分析~
这里引用一下网友的文章:dubbo源码结构目录分析
Dubbo Extension扩展点
Extension自适应扩展点是贯穿整个Dubbo 源码的一个常见技术,所以很重要~ 在上一节,我们基于这段代码:
//根据指定名称获取扩展点
Protocol myProtocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("myProtocol");
//自适应扩展点
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class). getAdaptiveExtension();
作为入口进行了简单的分析,这里我整理一下上节getAdaptiveExtension的流程图,当做复习,这里就不重复讲解了,有兴趣的可以看我上一篇的关于Dubbo Extension扩展点的介绍~
服务发布流程
首先我们要明确的是,我们使用Dubbo这款RPC框架,绕不过最重要的两个对象:
- 服务端 (发布服务,向注册中心注册服务)
- 消费端 (订阅服务,与服务端建立连接)
- 注册中心(协调服务端与消费端的中间成员)
- 监视器 (RPC调用次数和调用时间监控)
那么本节,我们主要讲Provider是怎么暴露服务,并向注册中心注册服务的?
首先我们思考:Dubbo的配置文件自定义的标签在Dubbo源码里如何实现?然后是Dubbo如何解析自定义的配置文件以及调用实现客户端与服务端之间的调用呢?
Spring对外留出的扩展
Dubbo是基于Spring 配置来实现服务的发布的,那么一定是基于Spring的扩展来写了一套自己的标签,那么spring是如何解析这些配置呢?
总的来说,就是可以通过Spring的扩展机制来扩展自己的标签。大家在Dubbo配置文件中看到的 <dubbo:registry>
、<dubbo:service>
等,就是基于Spring做的自定义扩展标签
在dubbo-config模块下的dubbo-config-spring模块,就是主要来做Spring扩展标签用的:
要实现自定义扩展,有三个步骤(在Spring中定义了两个接口,用来实现扩展)
- DubboNamespaceHandler接口: 注册一堆BeanDefinitionParser,利用他们来进行解析
- DubboBeanDefinitionParser接口:用于解析配置文件每个里element的内容
- Spring默认会加载jar包下的META-INF/spring.handlers文件寻找对应的NamespaceHandler
Dubbo的接入实现
当我们使用Dubbo给出的xml标签,Spring是如何完成解析的呢?
Dubbo中Spring扩展就是使用Spring的自定义类型,在dubbo-config-spring这一模块有两个类,分别对于Spring进行扩展:
- DubboNamespaceHandler类对于NamespaceHandlerSupport的扩展
这里很好理解,就是我们在dubbo配置文件里使用的自定义标签,将所有对应的标签内容转化为对应的Conifg配置类:
比如用户定义了这样的一个配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!-- 提供方应用信息,用于计算依赖关系 -->
<dubbo:application name="hello-world-app"/>
<!-- 使用multicast广播注册中心暴露服务地址 -->
<dubbo:registry id="zookeeper" address="zookeeper://192.168.200.111:2181" file="d:/dubbo-server" />
<!-- 声明需要暴露的服务接口 -->
<dubbo:reference id="demoService"
interface="com.test.dubbo.IGpHello"
registry="zookeeper"
version="1.0.1"/>
</beans>
这里的每个标签名在源码里对应一个配置类
标签名 | 源码配置类名称 |
---|---|
dubbo:application | ApplicationConfig.class |
dubbo:registry | RegistryConfig.class |
… | xxx.class |
我们以<dubbo:application>
为例,然后我们看对应application的配置类ApplicationConfig.class:
这里我就举一例足可说明问题,其他的字段参数可自行参考源码类比。
所以DubboNamespaceHandler类要做的事情就是根据用户配置的dubbo配置文件<dubbo:XXX>
,通过new DubboBeanDefinitionParser(parse里面有一个方法处理element的,这段代码很长,暂不贴出),遍历element元素,将值赋予对应的Config类~
- DubboBeanDefinitionParser类对于BeanDefinitionParser的扩展
BeanDefinitionParser全部都使用了DubboBeanDefinitionParser,如果我们想看<dubbo:service>
等标签的配置,就直接看DubboBeanDefinitionParser。这个里面主要做了一件事,把不同的配置分别转化成Spring容器中的bean对象
application对应ApplicationConfig
registry对应RegistryConfig
monitor对应MonitorConfig
provider对应ProviderConfig
consumer对应ConsumerConfig
…
为了在Spring启动的时候,也相应的启动provider发布服务注册服务的过程,而同时为了让客户端在启动的时候自动订阅发现服务,加入了两个BeanServiceBean、ReferenceBean。分别继承了ServiceConfig和ReferenceConfig同时还分别实现了InitializingBean、DisposableBean, ApplicationContextAware, ApplicationListener, BeanNameAware
- InitializingBean 接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是继承该接口的类,在初始化bean的时候会执行该方法。
- DisposableBean bean被销毁的时候,spring容器会自动执行destory方法,比如释放资源
- ApplicationContextAware 实现了这个接口的bean,当spring容器初始化的时候,会自动的将ApplicationContext注入进来
- ApplicationListener ApplicationEvent事件监听,spring容器启动后会发一个事件通知
- BeanNameAware 获得自身初始化时,本身的bean的id属性
那么基本的实现思路可以整理出来了~
- 利用Spring的解析收集xml中的配置信息,然后把这些配置信息存储到serviceConfig中
- 调用ServiceConfig的export方法来进行服务的发布和注册
服务发布流程解析
上面简单的梳理了一下dubbo-config-spring 这个模块的整体模型,接下来我们根据源码的思路,看看如何实现服务发布?
ServiceBean继承了InitializingBean,所以重载父类的afterPropertiesSet方法必然优先执行。
ServiceBean是服务发布的切入点,通过afterPropertiesSet方法,调用export()方法进行发布。
isDelay是做什么用的?
如果dubbo配置文件配置了这样的字段
<dubbo:provider delay="10" />
ServiceBean这里会判断一下
private boolean isDelay() {
Integer delay = getDelay();
ProviderConfig provider = getProvider();
if (delay == null && provider != null) {
delay = provider.getDelay();
}
return supportedApplicationListener && (delay == null || delay.intValue() == -1);
}
回过头来看,export为父类ServiceConfig中的方法,所以跳转到SeviceConfig类中的export方法
public class ServiceBean<T> extends ServiceConfig<T> {}
delay的使用
我们看ServiceBean的父类SeviceConfig.export()
public synchronized void export() {
if (provider != null) {
if (export == null) {
export = provider.getExport();
}
if (delay == null) {
delay = provider.getDelay();
}
}
if (export != null && ! export.booleanValue()) {
return;
}
if (delay != null && delay > 0) {
Thread thread = new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(delay);
} catch (Throwable e) {
}
doExport();
}
});
thread.setDaemon(true);
thread.setName("DelayExportServiceThread");
thread.start();
} else {
doExport();
}
}
Tips : delay由SeviceConfig的父类AbstractServiceConfig定义的Integer字段
我们发现,delay的作用就是延迟暴露,而延迟的方式也很直截了当 Thread.sleep(delay)
- export是synchronized修饰的方法。也就是说暴露的过程是原子操作,正常情况下不会出现锁竞争的问题,毕竟初始化过程大多数情况下都是单一线程操作,这里联想到了Spring的初始化流程,也进行了加锁操作,这里也给我们平时设计一个不错的启示:初始化流程的性能调优优先级应该放的比较低,但是安全的优先级应该放的比较高!
- 继续看doExport()方法。同样是一堆初始化代码
export的过程
继续看doExport(),最终会调用到doExportUrls()中:
private void doExportUrls() {
//是不是获得注册中心的配置
List<URL> registryURLs = loadRegistries(true);
//是不是支持多协议发布
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
启动一个服务的时候,做了什么事情?
调用注册中心发布服务到zookeeper,启动一个netty
通过debug到protocols处(我之前在配置文件里只配置了一个注册中心的地址)<dubbo:protocol name="dubbo" port="20888" id="dubbo" />
所以protocols处只有一个地址
protocols也是根据配置装配出来的。接下来让我们进入doExportUrlsFor1Protocol方法看看dubbo具体是怎么样将服务暴露出去的
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
// ...部分代码省略,有兴趣的同学可以参考文末中文版源码
//如果scope没有配置或者配置local、remote,dubbo会将服务export到本地,
// 意思就是:将服务以injvm协议export出去,如果是同一个jvm的应用可以直接通过jvm发起调用,
// 而不需要通过网络发起远程调用。
if ("injvm".equals(protocolConfig.getName())) {
protocolConfig.setRegister(false);
map.put("notify", "false");
}
// 导出服务
String contextPath = protocolConfig.getContextpath();
if ((contextPath == null || contextPath.length() == 0) && provider != null) {
contextPath = provider.getContextpath();
}
URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.hasExtension(url.getProtocol())) {
url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.getExtension(url.getProtocol()).getConfigurator(url).configure(url);
}
String scope = url.getParameter(Constants.SCOPE_KEY);
//配置为none不暴露
if (! Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
//配置不是remote的情况下做本地暴露 (配置为remote,则表示只暴露远程服务)
//发布服务
if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
exportLocal(url);
}
//如果配置不是local则暴露为远程服务.(配置为local,则表示只暴露本地服务)
//注册服务
if (! Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope) ){
if (logger.isInfoEnabled()) {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
//核心代码 :开始发布
if (registryURLs != null && registryURLs.size() > 0
&& url.getParameter("register", true)) {
for (URL registryURL : registryURLs) {//
url = url.addParameterIfAbsent("dynamic", registryURL.getParameter("dynamic"));
URL monitorUrl = loadMonitor(registryURL);
if (monitorUrl != null) {
url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
}
if (logger.isInfoEnabled()) {
logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
}
//通过proxyFactory来获取Invoker对象
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
//注册服务
Exporter<?> exporter = protocol.export(invoker);
//将exporter添加到list中
exporters.add(exporter);
}
} else {
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
Exporter<?> exporter = protocol.export(invoker);
exporters.add(exporter);
}
}
}
this.urls.add(url);
}
看到这里就比较明白dubbo的工作原理了doExportUrlsFor1Protocol方法,先创建两个URL,分别如下
dubbo://192.168.xx.63:20888/com.test.IGHello;
registry://192.168:20888/com.test.IGHello ;
是不是觉得这个URL很眼熟,没错!在注册中心看到的services的providers信息就是这个在上面这段代码中可以看到Dubbo的比较核心的抽象:Invoker, Invoker是一个代理类,从ProxyFactory中生成。
Tips:
- Invoker - 执行具体的远程调用(这块后续单独讲)
- Protocol – 服务地址的发布和订阅
- Exporter – 暴露服务或取消暴露
注册服务
前面讲了dubbo解析配置文件,赋值到对应的配置类,最终走到核心流程,将携带的URL进行注册服务:
//注册服务-核心流程
for (URL registryURL : registryURLs) {
url = url.addParameterIfAbsent("dynamic", registryURL.getParameter("dynamic"));
URL monitorUrl = loadMonitor(registryURL);
if (monitorUrl != null) {
url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
}
if (logger.isInfoEnabled()) {
logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
}
//step 1 :通过proxyFactory来获取Invoker对象
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
//step 2 :注册服务
Exporter<?> exporter = protocol.export(invoker);
//step 3 :将exporter添加到list中
exporters.add(exporter);
}
ok,源码到这里开始分剧情介绍了,这里主要是两个步骤:
- 通过proxyFactory来获取Invoker对象
- export 注册服务
我们先说export注册服务部分:
protocol来自ServiceConfig私有成员变量:
private static final Protocol protocol = ExtensionLoader.
getExtensionLoader(Protocol.class).
getAdaptiveExtension(); //Protocol$Adaptive
我们之前提到过,protocol 明显是一个自适应扩展点~
实际上这个Protocol得到的应该是一个Protocol$Adaptive
。一个自适应的适配器。这个时候,通过protocol.export(invoker),实际上调用的应该是Protocol$Adaptive这个动态类的export方法。我们再看看这段代码(这段代码,在上一节讲dubbo自适应扩展点时,debug时我曾贴出此段代码):
public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
public void destroy() {
throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
public int getDefaultPort() {
throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {
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);
}
public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null)
throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
com.alibaba.dubbo.common.URL url = arg0.getUrl();
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])");
Protocol extension = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);
return extension.export(arg0);
}
}
上面这段代码做两个事情
- 从url中获得protocol的协议地址,如果protocol为空,表示已dubbo协议发布服务,否则根据配置的协议类型来发布服务。
为什么叫基于适配器的扩展点?原因就在:如果我们请求的URL协议是Hession/RMI/…那么就要改代码
类似于if(“RMI”== URL),else if(“Hession”== URL)…,每个请求的协议都要加一个else if 判断,所以这里通过适配器,封装成统一的register://XXXd地址,自适应转换URL达成效果~ - 调用ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName)
这个方法的主要作用是用来获取ExtensionLoader实例代表的扩展的指定实现。已扩展实现的名字作为参数,结合前面学习getExtension的代码.:
@SuppressWarnings("unchecked")
public T getExtension(String name) {
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
if ("true".equals(name)) {
return getDefaultExtension();
}
//判断是否已经缓存过该扩展点
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(name);
}
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
//createExtension ,创建扩展点
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
通过debug看一下,此时invoke的携带的URL。
注:在上一小节,我们演示了dubbo的客户端与服务端的demo,这里我启动的的是dubbo的服务端的main函数debug模式:
public static void main( String[] args )
{
Main.main(new String[]{"spring","log4j"});
}
这个地址就是我们之前在zk节点上获取到拼接地址!
我们再回来看指定名称的自适应扩展点,那么这里返回的extension实际上是什么呢?
那就要看这里是什么类型的自适应扩展点了?这里明显是指定名称类型的, 当extName为registry的时候,我们不需要再次去阅读这块代码了,直接可以在扩展点中找到相应的实现扩展点[/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol] 配置如下:
因此,此时的extension就是com.alibaba.dubbo.registry.integration.RegistryProtocol,那么意味着,Protocol$Adaptive在最后返回的extension.export(arg0)就是传入的RegistryProtocol,则在step 2:注册服务后,进入RegistryProtocol方法的export:
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
//export invoker
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
//registry provider
final Registry registry = getRegistry(originInvoker);
//得到需要注册到zk上的协议地址,也就是dubbo://
final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);
registry.register(registedProviderUrl);
// 订阅override数据
// FIXME 提供者订阅时,会影响同一JVM即暴露服务,又引用同一服务的的场景,因为subscribed以服务名为缓存的key,导致订阅信息覆盖。
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
//保证每次export都返回一个新的exporter实例
return new Exporter<T>() {
public Invoker<T> getInvoker() {
return exporter.getInvoker();
}
public void unexport() {
try {
exporter.unexport();
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
try {
registry.unregister(registedProviderUrl);
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
try {
overrideListeners.remove(overrideSubscribeUrl);
registry.unsubscribe(overrideSubscribeUrl, overrideSubscribeListener);
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
};
}
上面又调用了几个方法,接下来我们分析doLocalExport,之后再回到主线剧情,分别来看:
doLocalExport
本地先启动监听服务
private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker){
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);
}
}
}
return (ExporterChangeableWrapper<T>) exporter;
}
上面代码中,protocol代码是怎么赋值的呢?我们看看代码,熟悉吗?是一个依赖注入的扩展点。不熟悉的话,我们再回想一下,在加载扩展点的时候,有一个injectExtension方法,针对已经加载的扩展点中的扩展点属性进行依赖注入。
private Protocol protocol;
public void setProtocol(Protocol protocol) {
this.protocol = protocol;
}
protocol.export
因此我们知道protocol是一个自适应扩展点,Protocol
A
d
a
p
t
i
v
e
,
然
后
调
用
这
个
自
适
应
扩
展
点
中
的
e
x
p
o
r
t
方
法
,
这
个
时
候
传
入
的
协
议
地
址
应
该
是
d
u
b
b
o
:
/
/
127.0.0.1
/
x
x
x
x
…
因
此
在
P
r
o
t
o
c
o
l
Adaptive,然后调用这个自适应扩展点中的export方法,这个时候传入的协议地址应该是 dubbo://127.0.0.1/xxxx… 因此在Protocol
Adaptive,然后调用这个自适应扩展点中的export方法,这个时候传入的协议地址应该是dubbo://127.0.0.1/xxxx…因此在ProtocolAdaptive.export方法中,ExtensionLoader.getExtension(Protocol.class).getExtension。应该就是基于DubboProtocol协议去发布服务了吗?如果是这样,那你们太单纯了。
这里并不是获得一个单纯的DubboProtocol扩展点,而是会通过Wrapper对Protocol进行装饰,装饰器分别为: ProtocolFilterWrapper/ ProtocolListenerWrapper; 至于MockProtocol为什么不在装饰器里面呢?大家再回想一下我们在看ExtensionLoader.loadFile这段代码的时候,有一个判断,装饰器必须要具备一个带有Protocol的构造方法,如下:
public ProtocolFilterWrapper(Protocol protocol){
if (protocol == null) {
throw new IllegalArgumentException("protocol == null");
}
this.protocol = protocol;
}
截止到这里,我们已经知道,Protocol$Adaptive里面的export方法,会调用ProtocolFilterWrapper以及ProtocolListenerWrapper类的方法
这两个装饰器是用来干嘛的呢?我们来分析下
ProtocolFilterWrapper和ProtocolListenerWrapper
- ProtocolFilterWrapper
这个类非常重要,dubbo机制里面日志记录、超时等等功能都是在这一部分实现的
这个类有3个特点:
- 它有一个参数为Protocol protocol的构造函数
- 它实现了Protocol接口
- 它使用责任链模式,对export和refer函数进行了封装,部分代码如下
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
return protocol.export(invoker);
}
return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
}
public void destroy() {
protocol.destroy();
}
//buildInvokerChain函数:它读取所有的filter类,利用这些类封装invoker
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
Invoker<T> last = invoker;
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);//自动激活扩展点,根据条件获取当前扩展可自动激活的实现
if (filters.size() > 0) {
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 Class<T> getInterface() {
return invoker.getInterface();
}
public URL getUrl() {
return invoker.getUrl();
}
public boolean isAvailable() {
return invoker.isAvailable();
}
public Result invoke(Invocation invocation) throws RpcException {
return filter.invoke(next, invocation);
}
public void destroy() {
invoker.destroy();
}
@Override
public String toString() {
return invoker.toString();
}
};
}
}
return last;
}
我们看如下文件: /dubbo-rpc-api/src/main/resources/METAINF/dubbo/internal/com.alibaba.dubbo.rpc.Filter
其实就是对Invoker,通过如下的Filter组装成一个责任链
echo=com.alibaba.dubbo.rpc.filter.EchoFilter
generic=com.alibaba.dubbo.rpc.filter.GenericFilter
genericimpl=com.alibaba.dubbo.rpc.filter.GenericImplFilter
token=com.alibaba.dubbo.rpc.filter.TokenFilter
accesslog=com.alibaba.dubbo.rpc.filter.AccessLogFilter
activelimit=com.alibaba.dubbo.rpc.filter.ActiveLimitFilter
classloader=com.alibaba.dubbo.rpc.filter.ClassLoaderFilter
context=com.alibaba.dubbo.rpc.filter.ContextFilter
consumercontext=com.alibaba.dubbo.rpc.filter.ConsumerContextFilter
exception=com.alibaba.dubbo.rpc.filter.ExceptionFilter
executelimit=com.alibaba.dubbo.rpc.filter.ExecuteLimitFilter
deprecated=com.alibaba.dubbo.rpc.filter.DeprecatedFilter
compatible=com.alibaba.dubbo.rpc.filter.CompatibleFilter
timeout=com.alibaba.dubbo.rpc.filter.TimeoutFilter
这其中涉及到很多功能,包括权限验证、异常、超时等等,当然可以预计计算调用时间等等应该也是在这其中的某个类实现的;这里我们可以看到export和refer过程都会被filter过滤
- ProtocolListenerWrapper
在这里我们可以看到export和refer分别对应了不同的Wrapper;export是对应的ListenerExporterWrapper。这块暂时先不去分析,因为这个地方并没有提供实现类。
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
return protocol.export(invoker);
}
return new ListenerExporterWrapper<T>(protocol.export(invoker),
Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class)
.getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY)));
}
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
return protocol.refer(type, url);
}
return new ListenerInvokerWrapper<T>(protocol.refer(type, url),
Collections.unmodifiableList(
ExtensionLoader.getExtensionLoader(InvokerListener.class)
.getActivateExtension(url, Constants.INVOKER_LISTENER_KEY)));
}
DubboProtocol.export
通过上面的代码分析完以后,最终我们能够定位到DubboProtocol.export方法。我们看一下dubboProtocol的export方法:openServer(url)
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl();
// export service.
String key = serviceKey(url);
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
exporterMap.put(key, exporter);
//export an stub service for dispaching event
Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY,Constants.DEFAULT_STUB_EVENT);
Boolean isCallbackservice = url.getParameter(Constants.IS_CALLBACK_SERVICE, false);
if (isStubSupportEvent && !isCallbackservice){
String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY);
if (stubServiceMethods == null || stubServiceMethods.length() == 0 ){
if (logger.isWarnEnabled()){
logger.warn(new IllegalStateException("consumer [" +url.getParameter(Constants.INTERFACE_KEY) +
"], has set stubproxy support event ,but no stub methods founded."));
}
} else {
stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
}
}
//暴露服务
openServer(url);
return exporter;
}
openServer
private void openServer(URL url) {
// find server.
String key = url.getAddress();//192.168.11.156:20880
//client 也可以暴露一个只有server可以调用的服务。
boolean isServer = url.getParameter(Constants.IS_SERVER_KEY,true);
if (isServer) {
ExchangeServer server = serverMap.get(key);
if (server == null) {//没有的话就是创建服务
serverMap.put(key, createServer(url));
} else {
//server支持reset,配合override功能使用
server.reset(url);
}
}
}
createServer
创建服务,开启心跳检测,默认使用netty。组装url
private ExchangeServer createServer(URL url) {
//默认开启server关闭时发送readonly事件
url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString());
//默认开启heartbeat
url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
String str = url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER);
if (str != null && str.length() > 0 && ! ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str))
throw new RpcException("Unsupported server type: " + str + ", url: " + url);
url = url.addParameter(Constants.CODEC_KEY, Version.isCompatibleVersion() ? COMPATIBLE_CODEC_NAME : DubboCodec.NAME);
ExchangeServer server;
try {
server = Exchangers.bind(url, requestHandler);
} catch (RemotingException e) {
throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
}
str = url.getParameter(Constants.CLIENT_KEY);
if (str != null && str.length() > 0) {
Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
if (!supportedTypes.contains(str)) {
throw new RpcException("Unsupported client type: " + str);
}
}
return server;
}
Exchangers.bind
public static ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
if (handler == null) {
throw new IllegalArgumentException("handler == null");
}
url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
return getExchanger(url).bind(url, handler);
}
getExchanger
通过ExtensionLoader获得指定的扩展点,type默认为header
public static Exchanger getExchanger(URL url) {
//url中获得exchanger, 默认为header
String type = url.getParameter(Constants.EXCHANGER_KEY, Constants.DEFAULT_EXCHANGER);
return getExchanger(type);
}
public static Exchanger getExchanger(String type) {
return ExtensionLoader.getExtensionLoader(Exchanger.class).getExtension(type);
}
HeaderExchanger.bind
调用headerExchanger的bind方法
public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}
Transporters.bind
通过transporter.bind来进行绑定。
public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
if (handlers == null || handlers.length == 0) {
throw new IllegalArgumentException("handlers == null");
}
ChannelHandler handler;
if (handlers.length == 1) {
handler = handlers[0];
} else {
handler = new ChannelHandlerDispatcher(handlers);
}
return getTransporter().bind(url, handler);
}
NettyTransport.bind
通过NettyTranport创建基于Netty的server服务
public Server bind(URL url, ChannelHandler listener) throws RemotingException {
return new NettyServer(url, listener);
}
new HeaderExchangeServer
在调用HeaderExchanger.bind方法的时候,是先new一个HeaderExchangeServer. 这个server是干嘛呢? 是对当前这个连接去建立心跳机制
public class HeaderExchangeServer implements ExchangeServer {
private final ScheduledExecutorService scheduled = Executors.
newScheduledThreadPool(1,new NamedThreadFactory(
"dubbo-remoting-server-heartbeat", true));
// 心跳定时器
private ScheduledFuture<?> heatbeatTimer;
// 心跳超时,毫秒。缺省0,不会执行心跳。
private int heartbeat;
private int heartbeatTimeout;
private final Server server;
private volatile boolean closed = false;
public HeaderExchangeServer(Server server) {
//..属性赋值
//心跳
startHeatbeatTimer();
}
private void startHeatbeatTimer() {
//关闭心跳定时
stopHeartbeatTimer();
if (heartbeat > 0) {
//每隔heartbeat时间执行一次
heatbeatTimer = scheduled.scheduleWithFixedDelay(
new HeartBeatTask( new HeartBeatTask.ChannelProvider() {
//获取channels
public Collection<Channel> getChannels() {
return Collections.unmodifiableCollection(
HeaderExchangeServer.this.getChannels() );
}
}, heartbeat, heartbeatTimeout),
heartbeat, heartbeat,TimeUnit.MILLISECONDS);
}
}
//关闭心跳定时
private void stopHeartbeatTimer() {
try {
ScheduledFuture<?> timer = heatbeatTimer;
if (timer != null && ! timer.isCancelled()) {
timer.cancel(true);
}
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
} finally {
heatbeatTimer =null;
}
}
心跳线程HeartBeatTask
在超时时间之内,发送数据
在超时时间在外,是客户端的话,重连;是服务端,那么关闭
服务发布总结
直接从官方网站上扒了一个图过来,好这个图显示的很清楚了。
通过proxyFactory来获取Invoker对象
前面在ServiceConfig主线上剧情:
- 通过proxyFactory来获取Invoker对象
- 注册服务
接下来,我们说看一下通过proxyFactory来获取Invoker对象这一过程
//注册服务-核心流程
for (URL registryURL : registryURLs) {
url = url.addParameterIfAbsent("dynamic", registryURL.getParameter("dynamic"));
URL monitorUrl = loadMonitor(registryURL);
if (monitorUrl != null) {
url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
}
if (logger.isInfoEnabled()) {
logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
}
//step 1 :通过proxyFactory来获取Invoker对象
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
//step 2 :注册服务
Exporter<?> exporter = protocol.export(invoker);
//step 3 :将exporter添加到list中
exporters.add(exporter);
}
proxyFactory是什么呢?
private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
明显是一个扩展点,那此时的proxyFactory实际上是?
定义在方法上的@Adaptice,所以应该是ProxyFactory$Adaptice
通过ExtensionLoader.createAdaptiveExtensionClass,我们debug一下,拿到了这个
String extName = url.getParameter(“proxy”, “javassist”);
这里的自适应扩展点是根据proxy获取的,如果没有默认是javassist,所以,我们看看配置文件对应的配置
javassist=com.alibaba.dubbo.rpc.proxy.javassist.JavassistProxyFactory
所以看com.alibaba.dubbo.rpc.proxy.javassist.JavassistProxyFactory
也就是说:
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
这一步,获取Invoker是在JavassistProxyFactory实现的!同样,如果是debug也是证实我们的猜想:
接下来,看看Wrapper.getWrapper里面做了什么事情?
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
// TODO Wrapper类不能正确处理带$的类名
final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
return new AbstractProxyInvoker<T>(proxy, type, url) {
@Override
protected Object doInvoke(T proxy, String methodName,
Class<?>[] parameterTypes,
Object[] arguments) throws Throwable {
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
};
}
这里我们断点看一下,动态生成的Wrapper:
我这里拿出c3:
取到服务端的服务了~
接下来回到主线:
也证实了我们之前的猜想,此时通过动态生成的invoke的对象是JavassistProxyFactory,然后,此时主线剧情就是:
拼装好URL,并且通过代理拿到服务端的实例,接下来export到网络层传输服务~
这一小段总结:
服务注册流程
前面,我们已经知道,基于Spring这个解析入口,到发布服务的过程,接着基于DubboProtocol去发布,最终调用Netty的api创建了一个NettyServer。
那么继续沿着RegistryProtocol.export这个方法,来看看注册服务的代码
RegistryProtocol.export
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
//export invoker
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker); //发布本地服务
//registry provider
final Registry registry = getRegistry(originInvoker);
final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);
registry.register(registedProviderUrl);
// 订阅override数据
// FIXME 提供者订阅时,会影响同一JVM即暴露服务,又引用同一服务的的场景,因为subscribed以服务名为缓存的key,导致订阅信息覆盖。
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
//保证每次export都返回一个新的exporter实例
return new Exporter<T>() {
public Invoker<T> getInvoker() {
return exporter.getInvoker();
}
public void unexport() {
try {
exporter.unexport();
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
try {
registry.unregister(registedProviderUrl);
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
try {
overrideListeners.remove(overrideSubscribeUrl);
registry.unsubscribe(overrideSubscribeUrl, overrideSubscribeListener);
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
};
}
Tips:前面我提到的doLocalExport这个方法到这里,算是将服务发布的流程讲完,现在我们回到主线,看看另外一个重要的步骤:服务注册getRegistry~
getRegistry
这个方法是invoker的地址获取registry实例
/**
* 根据invoker的地址获取registry实例
* @param originInvoker
* @return
*/
private Registry getRegistry(final Invoker<?> originInvoker){
URL registryUrl = originInvoker.getUrl(); //获得registry://192.168.200.111:2181的协议地址
if (Constants.REGISTRY_PROTOCOL.equals(registryUrl.getProtocol())) {
//得到zookeeper的协议地址
String protocol = registryUrl.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_DIRECTORY);
//registryUrl就会变成了zookeeper://192.168.200.111
registryUrl = registryUrl.setProtocol(protocol).removeParameter(Constants.REGISTRY_KEY);
}
//registryFactory是什么?
return registryFactory.getRegistry(registryUrl);
}
registryFactory.getRegistry
这段代码很明显了,通过前面这段代码的分析,其实就是把registry的协议头改成服务提供者配置的协议地址,也就是我们配置的
<dubbo:registry address=”zookeeper://192.168.200.111:2181”/>
然后registryFactory.getRegistry的目的,就是通过协议地址匹配到对应的注册中心。那registryFactory是一个什么样的对象呢?,我们找一下这个代码的定义
private RegistryFactory registryFactory;
public void setRegistryFactory(RegistryFactory registryFactory) {
this.registryFactory = registryFactory;
}
这个代码有点眼熟,再来看看RegistryFactory这个类的定义,我猜想一定是一个扩展点,不信,咱们看
并且,大家还要注意这里面的一个方法上,有一个@Adaptive的注解,说明什么? 这个是一个自适应扩展点。按照我们之前看过代码,自适应扩展点加在方法层面上,表示会动态生成一个自适应的适配器。所以这个自适应适配器应该是RegistryFactory$Adaptive
@SPI("dubbo")
public interface RegistryFactory {
/**
* 连接注册中心.
*
* 连接注册中心需处理契约:<br>
* 1. 当设置check=false时表示不检查连接,否则在连接不上时抛出异常。<br>
* 2. 支持URL上的username:password权限认证。<br>
* 3. 支持backup=10.20.153.10备选注册中心集群地址。<br>
* 4. 支持file=registry.cache本地磁盘文件缓存。<br>
* 5. 支持timeout=1000请求超时设置。<br>
* 6. 支持session=60000会话超时或过期设置。<br>
*
* @param url 注册中心地址,不允许为空
* @return 注册中心引用,总不返回空
*/
@Adaptive({"protocol"})
Registry getRegistry(URL url);
}
RegistryFactory$Adaptive
我们拿到这个动态生成的自适应扩展点,看看这段代码里面的实现
- 从url中拿到协议头信息,这个时候的协议头是zookeeper://
- 通过ExtensionLoader.getExtensionLoader(RegistryFactory.class).getExtension(“zookeeper”)去获得一个指定的扩展点,而这个扩展点的配置在
dubbo-registry-zookeeper/resources/META-INF/dubbo/internal/com.alibaba.dubbo.registry.RegistryFactory。得到一个ZookeeperRegistryFactory
public class RegistryFactory$Adaptive implements com.alibaba.dubbo.registry.RegistryFactory {
public com.alibaba.dubbo.registry.Registry getRegistry(com.alibaba.dubbo.common.URL arg0) {
if (arg0 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg0;
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.registry.RegistryFactory) " +
"name from url(" + url.toString() + ") use keys([protocol])");
com.alibaba.dubbo.registry.RegistryFactory extension =
(com.alibaba.dubbo.registry.RegistryFactory)
ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.registry.RegistryFactory.class).
getExtension(extName);
return extension.getRegistry(arg0);
}
}
ZookeeperRegistryFactory
这个方法中并没有getRegistry方法,而是在父类AbstractRegistryFactory
- 从缓存REGISTRIES中,根据key获得对应的Registry
- 如果不存在,则创建Registry
public Registry getRegistry(URL url) {
url = url.setPath(RegistryService.class.getName())
.addParameter(Constants.INTERFACE_KEY, RegistryService.class.getName())
.removeParameters(Constants.EXPORT_KEY, Constants.REFER_KEY);
String key = url.toServiceString();
// 锁定注册中心获取过程,保证注册中心单一实例
LOCK.lock();
try {
Registry registry = REGISTRIES.get(key);
if (registry != null) {
return registry;
}
registry = createRegistry(url);
if (registry == null) {
throw new IllegalStateException("Can not create registry " + url);
}
REGISTRIES.put(key, registry);
return registry;
} finally {
// 释放锁
LOCK.unlock();
}
}
createRegistry
创建一个注册中心,这个是一个抽象方法,具体的实现在对应的子类实例中实现的,在ZookeeperRegistryFactory中
public Registry createRegistry(URL url) {
return new ZookeeperRegistry(url, zookeeperTransporter);
}
通过zkClient,获得一个zookeeper的连接实例
public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
super(url);
if (url.isAnyHost()) {
throw new IllegalStateException("registry address == null");
}
String group = url.getParameter(Constants.GROUP_KEY, DEFAULT_ROOT);
if (! group.startsWith(Constants.PATH_SEPARATOR)) {
group = Constants.PATH_SEPARATOR + group;
}
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);
}
}
}
});
}
代码分析到这里,我们对于getRegistry得出了一个结论,根据当前注册中心的配置信息,获得一个匹配的注册中心,也就是ZookeeperRegistry
registry.register(registedProviderUrl);
好的,拿到了ZookeeperRegistry我们回到export主线,往下分析:
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
// step 1 : export invoker
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
//step 2 : registry provider
final Registry registry = getRegistry(originInvoker);
//得到需要注册到zk上的协议地址,也就是dubbo://
final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);
registry.register(registedProviderUrl);
// 订阅override数据
// FIXME 提供者订阅时,会影响同一JVM即暴露服务,又引用同一服务的的场景,因为subscribed以服务名为缓存的key,导致订阅信息覆盖。
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
//保证每次export都返回一个新的exporter实例
return new Exporter<T>() {
public Invoker<T> getInvoker() {
return exporter.getInvoker();
}
public void unexport() {
try {
exporter.unexport();
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
try {
registry.unregister(registedProviderUrl);
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
try {
overrideListeners.remove(overrideSubscribeUrl);
registry.unsubscribe(overrideSubscribeUrl, overrideSubscribeListener);
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
};
}
会调用registry.register去讲dubbo://的协议地址注册到zookeeper上.
这个方法会调用FailbackRegistry类中的register. 为什么呢?因为ZookeeperRegistry这个类中并没有register这个方法,但是他的父类FailbackRegistry中存在register方法,而这个类又重写了AbstractRegistry类中的register方法。所以我们可以直接定位FailbackRegistry这个类中的register方法中
FailbackRegistry.register
- FailbackRegistry,从名字上来看,是一个失败重试机制
- 调用父类的register方法,讲当前url添加到缓存集合中
- 调用doRegister方法,这个方法很明显,是一个抽象方法,会由ZookeeperRegistry子类实现。
@Override
public void register(URL url) {
super.register(url);
failedRegistered.remove(url);
failedUnregistered.remove(url);
try {
// Core!! -> 向服务器端发送注册请求
doRegister(url);
} catch (Exception e) {
Throwable t = e;
// 如果开启了启动时检测,则直接抛出异常
boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)
&& url.getParameter(Constants.CHECK_KEY, true)
&& ! Constants.CONSUMER_PROTOCOL.equals(url.getProtocol());
boolean skipFailback = t instanceof SkipFailbackWrapperException;
if (check || skipFailback) {
if(skipFailback) {
t = t.getCause();
}
throw new IllegalStateException("Failed to register " + url + " to registry " + getUrl().getAddress() + ", cause: " + t.getMessage(), t);
} else {
logger.error("Failed to register " + url + ", waiting for retry, cause: " + t.getMessage(), t);
}
// 将失败的注册请求记录到失败列表,定时重试
failedRegistered.add(url);
}
}
ZookeeperRegistry.doRegister
这里才是真正的向服务器端发送注册请求 doRegister(url);
终于找到你了,调用zkclient.create在zookeeper中创建一个节点。
protected void doRegister(URL url) {
try {
zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true));
} catch (Throwable e) {
throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
后记
欢迎关注本套Java系列文章-地址导航:Java架构师成长之路
Dubbo中文注释版:DubboV2.5.4下载地址