一、解析服务
Spring遇到 <dubbo:......>
时,根据 spring.handlers
回调 DubboNameSpaceHandler
,使用 DubboBeanDefinitionParser
来解析。此处涉及一个 parser
方法,用于将 xml 配置信息定义为 spring 的 BeanDefinition
,其包含类名、scope、属性、构造函数参数列表、依赖 Bean、是否是单例类、是否是懒加载等信息,之后的操作直接对 BeanDefinition
进行,可根据其中参数利用反射进行对象创建。
解析服务
基于 dubbo.jar 内的
META-INF/spring.handlers
配置,Spring 在遇到 dubbo 名称空间时,会回调DubboNamespaceHandler
。所有 dubbo 的标签,都统一用
DubboBeanDefinitionParser
进行解析,基于一对一属性映射,将 XML 标签解析为 Bean 对象。在
ServiceConfig.export()
或ReferenceConfig.get()
初始化时,将 Bean 对象转换 URL 格式,所有 Bean 属性转成 URL 的参数。然后将 URL 传给协议扩展点,基于扩展点的扩展点自适应机制,根据 URL 的协议头,进行不同协议的服务暴露或引用。
二、暴露服务
暴露服务对应的 ServiceConfig.export
和引用服务对应的 ReferenceConfig.get
初始化时,Bean 转化为 URL,Bean 的属性转化为 URL 参数。然后由协议拓展点处理 URL,根据 URL 的协议头进行不同协议的操作。
此为暴露服务时序图
暴露服务
1. 只暴露服务端口:
在没有注册中心,直接暴露提供者的情况下,
ServiceConfig
解析出的 URL 的格式为:dubbo://service-host/com.foo.FooService?version=1.0.0
。基于扩展点自适应机制,通过 URL 的
dubbo://
协议头识别,直接调用DubboProtocol
的export()
方法,打开服务端口。2. 向注册中心暴露服务:
在有注册中心,需要注册提供者地址的情况下,
ServiceConfig
解析出的 URL 的格式为:registry://registry-host/org.apache.dubbo.registry.RegistryService?export=URL.encode("dubbo://service-host/com.foo.FooService?version=1.0.0")
。基于扩展点自适应机制,通过 URL 的
registry://
协议头识别,就会调用RegistryProtocol
的export()
方法,将export
参数中的提供者 URL,先注册到注册中心。再重新传给
Protocol
扩展点进行暴露:dubbo://service-host/com.foo.FooService?version=1.0.0
,然后基于扩展点自适应机制,通过提供者 URL 的dubbo://
协议头识别,就会调用DubboProtocol
的export()
方法,打开服务端口。
<dubbo:service>
先被解析成 ServiceBean
,由 ServiceBean
实现接口 ApplicationContextAware
,调用 setApplicationContext
方法,为后续初始化做基础。
由ServiceBean
实现接口InitializingBean
,调用 afterPropertiesSet
方法,依次解析<dubbo:provider>
,<dubbo:application>
,<dubbo:module>
,<dubbo:registry>
,<dubbo:monitor>
,<dubbo:protocol>
(新版本中可能不止这些),同时进行export
。
最后ServiceBean
实现接口ApplicationListener<ContextRefreshedEvent>
,调用onApplicationEvent
方法,进行export
。(二三两步中都有export
操作,目前认识为防止未执行)。
ServiceBean
拓展了ServiceConfig
,调用export
方法时由ServiceConfig
完成服务暴露发功能实现。
public synchronized void export() {
checkAndUpdateSubConfigs();//老版本中doExport()里对各种的检查
if (!shouldExport()) {
return;
}
if (shouldDelay()) {
DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
} else {
doExport();
}
}
//上半部分为笔者使用的,也是写的时候最新的源码里的代码
//下半部分为网络中找到的,可能单独拿出来更清楚的以前的源码
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();
}
}
Dubbo 当前的源码为上半部分,将一些判断内容放到其他方法里实现。export
的作用为判断服务是否已经打开以及是否需要延迟打开,老版本中doExport
方法中会进行参数检查和设置,包括泛化调用,本地实现,本地存根,本地伪装和配置,故实际逻辑还在方法doExport
里。(新版本中,这些被移到了checkAndUpdateSubConfigs
方法中(export
中))
//此为新版本里的doExport()
protected synchronized void doExport() {
if (unexported) {
throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");
}
if (exported) {
return;
}
exported = true;
if (StringUtils.isEmpty(path)) {
path = interfaceName;
}
doExportUrls();
}
检查完参数后,开始暴露服务。doExportUrls
表示,Dubbo支持多协议和多注册中心。
private void doExportUrls() {
List<URL> registryURLs = loadRegistries(true);
for (ProtocolConfig protocolConfig : protocols) {
String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
ApplicationModel.initProviderModel(pathKey, providerModel);//生成的serviceBean在这里
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
这中途涉及了另一个方法loadRegistries
。
protected List<URL> loadRegistries(boolean provider) {
// check && override if necessary
List<URL> registryList = new ArrayList<URL>();
if (CollectionUtils.isNotEmpty(registries)) {
for (RegistryConfig config : registries) {
String address = config.getAddress();
if (StringUtils.isEmpty(address)) {
address = ANYHOST_VALUE;
}
if (!RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
Map<String, String> map = new HashMap<String, String>();
appendParameters(map, application);
appendParameters(map, config);
map.put(PATH_KEY, RegistryService.class.getName());
appendRuntimeParameters(map);
if (!map.containsKey(PROTOCOL_KEY)) {
map.put(PROTOCOL_KEY, DUBBO_PROTOCOL);
}
List<URL> urls = UrlUtils.parseURLs(address, map);
for (URL url : urls) {
url = URLBuilder.from(url)
.addParameter(REGISTRY_KEY, url.getProtocol())
.setProtocol(REGISTRY_PROTOCOL)
.build();
if ((provider && url.getParameter(REGISTER_KEY, true))
|| (!provider && url.getParameter(SUBSCRIBE_KEY, true))) {
registryList.add(url);
}
}
}
}
}
return registryList;
}
该方法将registries
变量里每个地址拼上application
和registryConfig
里的参数,拼成一个registryUrl
带参数的标准格式。然后返回这些url的列表。
然后针对每个协议与注册中心,开始组装URL(主要使用doExportUrlsFor1Protocol
方法),并且会转成invoker以及创建expoter,给一个service标签的serviceBean生成了一个代理好的Invoker,该invoker放在exporter对象下,exporter在serviceBean的一个exporters变量下,准备被调用。
首先
ServiceConfig
类拿到对外提供服务的实际类 ref (如:HelloWorldImpl
),然后通过ProxyFactory
类的getInvoker
方法使用 ref 生成一个AbstractProxyInvoker
实例,到这一步就完成具体服务到Invoker
的转化。接下来就是Invoker
转换到Exporter
的过程。Dubbo 处理服务暴露的关键就在
Invoker
转换到Exporter
的过程。
根据 scope 来决定进行本地暴露还是远程暴露还是不暴露。
- 本地暴露(提供者同时也是消费者时,没必要通过网络通信去获取自己提供的服务):
private void exportLocal(URL url) {
URL local = URLBuilder.from(url)
.setProtocol(LOCAL_PROTOCOL)
.setHost(LOCALHOST_VALUE)
.setPort(0)
.build();
Exporter<?> exporter = protocol.export(
PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local));
exporters.add(exporter);
logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry url : " + local);
}
通过代理创建 Invoker,本地暴露使用 injvm 协议,injvm 是伪协议,不开启端口,不能被远程调用,只在 JVM 内直接关联,但执行 Dubbo 的 Filter 链。
- 远程暴露:
// export to remote if the config is not local (export to local only when config is local)
if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
if (!isOnlyInJvm() && logger.isInfoEnabled()) {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
if (CollectionUtils.isNotEmpty(registryURLs)) {
for (URL registryURL : registryURLs) {
//if protocol is only injvm ,not register
if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
continue;
}
url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
URL monitorUrl = loadMonitor(registryURL);
if (monitorUrl != null) {
url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
}
if (logger.isInfoEnabled()) {
logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
}
// For providers, this is used to enable custom proxy to generate invoker
String proxy = url.getParameter(PROXY_KEY);
if (StringUtils.isNotEmpty(proxy)) {
registryURL = registryURL.addParameter(PROXY_KEY, proxy);
}
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
} else {
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
/**
* @since 2.7.0
* ServiceData Store
*/
MetadataReportService metadataReportService = null;
if ((metadataReportService = getMetadataReportService()) != null) {
metadataReportService.publishProvider(url);
}
}
此时服务的暴露有两种:使用注册中心和不使用注册中心。不使用的情况下,直接暴露对应协议的服务,引用服务只能通过直连的方式;使用的情况下,引用服务可以通过注册中心动态获取提供者列表,也可以通过直连方式引用。
组装完的URL基于扩展点自适应机制,由协议头调用对应协议的export
来暴露服务。(即开头为"dubbo://"则调用DubboProtocol
的export
方法;开头为"registry://"则调用RegistryProtocol
的export
方法...)
1. 只暴露服务端口:
在没有注册中心,直接暴露提供者的情况下,
ServiceConfig
解析出的 URL 的格式为:dubbo://service-host/com.foo.FooService?version=1.0.0
。基于扩展点自适应机制,通过 URL 的
dubbo://
协议头识别,直接调用DubboProtocol
的export()
方法,打开服务端口。2. 向注册中心暴露服务:
在有注册中心,需要注册提供者地址的情况下,
ServiceConfig
解析出的 URL 的格式为:registry://registry-host/org.apache.dubbo.registry.RegistryService?export=URL.encode("dubbo://service-host/com.foo.FooService?version=1.0.0")
,基于扩展点自适应机制,通过 URL 的
registry://
协议头识别,就会调用RegistryProtocol
的export()
方法,将export
参数中的提供者 URL,先注册到注册中心。再重新传给
Protocol
扩展点进行暴露:dubbo://service-host/com.foo.FooService?version=1.0.0
,然后基于扩展点自适应机制,通过提供者 URL 的dubbo://
协议头识别,就会调用DubboProtocol
的export()
方法,打开服务端口。
此处笔者列举一下Dubbo文档中的源码解读中服务暴露部分与目前源码中有不同的地方(笔者一开始用的最新的源码,目前官网源码解读文档为2.6.4版本匹配的,大家不要步我后尘)以及感觉有点疑惑的地方。
-
定位:2. 源码解读
文档中提示注意
isDelay
方法,该方法返回true意为“无需延迟导出”,返回false意为”需要延迟导出“,和一般理解有出入,会让人有困惑。源码中,在ServiceConfig
下使用了shouldDelay
方法,解决了该问题。 -
定位:2.1.1 检查配置
文档中获取export和delay来分析是否导出以及是否如何延迟导出,源码中采用调用
shouldExport
和shouldDelay
,使得export
方法更简洁。文档中的
doExport
方法过于冗长,其原因是进行了配置检查,包括:检测<dubbo:service>
标签的interface属性合法性,不合法抛出异常;检测ProviderConfig
、ApplicationConfig
等核心配置类对象是否为空,若为空,则尝试从其他配置类对象中获取相应的实例;检测并处理泛化服务和普通服务类;检测本地存根配置,并进行相应的处理;对ApplicationConfig
、RegistryConfig
等配置类进行检测,为空则尝试创建,若无法创建则抛出异常。而在源码中,我们发现doExport
方法简短,其检查配置部分在export
时便调用checkAndUpdateSubConfigs
完成。 -
定位:2.2 导出Dubbo服务
这里针对scope参数的处理为:
scope == none
→ 不导出服务;scope != remote
→ 导出到本地;scope != local
→ 导出到远程。是不是代表scope不为none且同时不为remote和local就同时导出到本地和远程?但是源码里这里注释为export to remote only when config is remote
以及export to local only when config is local
。
-
-
定位:2.2.2 导出服务到本地
exportLocal
中使用了URLBuilder
代替原有的构造修改URL方法。 -
定位:2.2.3 导出服务到远程
RegistryProtocol
下的export
也有些许改变。将providerUrl
也加入doLocalExport
的参数里,同时将overrideSubscUrl
和registeredProviderUrl
塞进exporter里,使得DestroyableExporter
的参数减少。doLocalExport
中简化格式,利用computeIfAbsent
取代了原先的双重检查锁。文档中
createServer
方法内第二行url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY,
疑似缺少一部分,根据源码推测应为url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString())
。 -
定位:2.2.4.1 创建注册中心
文档中
CuratorZookeeperTransporter
类中的connect
方法在源码中不存在。接口ZookeeperTransporter
的connect
方法由AbstractZookeeperTransporter
实现。CuratorZookeeperTransporter
类继承AbstractZookeeperTransporter
类并实现createZookeeperClient
方法再由AbstractZookeeperTransporter
调用。
服务导出到远程的过程:
从RegistryProtocol.export
开始
-
调用
RegistryProtocol.doLocalExport
导出服务双重锁检查 (源码中有更改)
-
protocol.export
根据协议在运行时会自动加载,例如DubboProtocol.export
DubboExporter
的创建-
DubboProtocol.openServer
→DubboProtocol.createServer
检测是否存在 server 参数所代表的 Transporter 拓展,不存在则抛出异常 (代码直白,略)
-
创建服务器实例 (
Exchangers.bind
)-
Exchangers.bind
→HeaderExchanger.bind
,包含三层逻辑new HeaderExchangeHandler(handler)
new DecodeHandler(new HeaderExchangeHandler(handler))
-
Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))
-
Transporters.getTransporter
是动态创建的,类名为TransporterAdaptive
,即自适应拓展类,根据传入的URL参数决定加载什么类型的Transporter,默认为NettyTransporter
。 (NettyTransporter.bind
→NettyServer
→AbstractServer
→NettyServer.doOpen
-
-
检测是否支持 client 参数所表示的 Transporter 拓展,不存在也是抛出异常 (代码直白,略)
-
向注册中心注册服务 (
RegistryProtocol.register
)-
获取注册中心实例 (
AbstractRegistryFactory.getRegistry
)-
访问缓存,未命中则调用
createRegistry
创建Registry-
ZookeeperRegistryFactory.createRegistry
→ZookeeperRegistry
获取组名,默认为dubbo
-
创建Zookeeper客户端,默认为
CuratorZookeeperTransporter
(ZookeeperTransporter.connect
,此处文档和最新源码有出入,CuratorZookeeperTransporter.connect
不存在,但CuratorZookeeperTransporter.createZookeeperClient
→CuratorZookeeperClient
)-
CuratorZookeeperClient
构造方法主要用于创建和启动CuratorFramework
实例,大部分为Curator框架的代码,可参考Curator官方文档
-
添加状态监听器
-
写入缓存
-
-
向注册中心注册服务 (将服务配置数据写入到Zookeeper的某个路径的节点下)
-
FailbackRegistry.registry
→ZookeeperRegistry.doRegistry
toUrlPath
方法生成节点路径-
AbstractZookeeperClient.create
通过递归创建当前节点的上一个路径
-
根据ephemeral的值决定创建临时节点还是持久节点 (
createEphemeral
,createPersistent
)只需分析其中一个,以
createEphemeral
为例CuratorZookeeperClient.createEphemeral
,通过Curator框架创建节点
-
-
向注册中心进行订阅 override 数据 (非本章重点)
创建并返回
DestroyableExporter
(无难度,略)