看看Dubbo源码

一、解析服务

Spring遇到 时,根据 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:// 协议头识别,直接调用 DubboProtocolexport() 方法,打开服务端口。

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:// 协议头识别,就会调用 RegistryProtocolexport() 方法,将 export 参数中的提供者 URL,先注册到注册中心。

再重新传给 Protocol 扩展点进行暴露: dubbo://service-host/com.foo.FooService?version=1.0.0,然后基于扩展点自适应机制,通过提供者 URL 的 dubbo:// 协议头识别,就会调用 DubboProtocolexport() 方法,打开服务端口。

先被解析成 ServiceBean,由 ServiceBean 实现接口 ApplicationContextAware,调用 setApplicationContext 方法,为后续初始化做基础。

ServiceBean实现接口InitializingBean,调用 afterPropertiesSet方法,依次解析(新版本中可能不止这些),同时进行export

最后ServiceBean实现接口ApplicationListener,调用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 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 loadRegistries(boolean provider) {
 // check && override if necessary
 List registryList = new ArrayList();
 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 map = new HashMap();
 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 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变量里每个地址拼上applicationregistryConfig里的参数,拼成一个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://"则调用DubboProtocolexport方法;开头为"registry://"则调用RegistryProtocolexport方法...)

1. 只暴露服务端口:

在没有注册中心,直接暴露提供者的情况下,ServiceConfig 解析出的 URL 的格式为: dubbo://service-host/com.foo.FooService?version=1.0.0

基于扩展点自适应机制,通过 URL 的 dubbo:// 协议头识别,直接调用 DubboProtocolexport() 方法,打开服务端口。

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:// 协议头识别,就会调用RegistryProtocolexport() 方法,将 export 参数中的提供者 URL,先注册到注册中心。

再重新传给 Protocol 扩展点进行暴露: dubbo://service-host/com.foo.FooService?version=1.0.0,然后基于扩展点自适应机制,通过提供者 URL 的 dubbo:// 协议头识别,就会调用 DubboProtocolexport() 方法,打开服务端口。


此处笔者列举一下Dubbo文档中的源码解读中服务暴露部分与目前源码中有不同的地方(笔者一开始用的最新的源码,目前官网源码解读文档为2.6.4版本匹配的,大家不要步我后尘)以及感觉有点疑惑的地方。

  • 定位:2. 源码解读

    文档中提示注意isDelay方法,该方法返回true意为“无需延迟导出”,返回false意为”需要延迟导出“,和一般理解有出入,会让人有困惑。源码中,在ServiceConfig下使用了shouldDelay方法,解决了该问题。

  • 定位:2.1.1 检查配置

    文档中获取export和delay来分析是否导出以及是否如何延迟导出,源码中采用调用 shouldExportshouldDelay,使得 export 方法更简洁。

    文档中的doExport方法过于冗长,其原因是进行了配置检查,包括:检测 标签的interface属性合法性,不合法抛出异常;检测ProviderConfigApplicationConfig 等核心配置类对象是否为空,若为空,则尝试从其他配置类对象中获取相应的实例;检测并处理泛化服务和普通服务类;检测本地存根配置,并进行相应的处理;对 ApplicationConfigRegistryConfig 等配置类进行检测,为空则尝试创建,若无法创建则抛出异常。而在源码中,我们发现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的参数里,同时将overrideSubscUrlregisteredProviderUrl塞进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方法在源码中不存在。接口ZookeeperTransporterconnect方法由AbstractZookeeperTransporter实现。CuratorZookeeperTransporter类继承AbstractZookeeperTransporter类并实现createZookeeperClient方法再由AbstractZookeeperTransporter调用。


服务导出到远程的过程:

RegistryProtocol.export 开始

  1. 调用 RegistryProtocol.doLocalExport 导出服务

    1. 双重锁检查 (源码中有更改)

    2. protocol.export 根据协议在运行时会自动加载,例如DubboProtocol.export

      1. DubboExporter的创建

      2. DubboProtocol.openServerDubboProtocol.createServer

        1. 检测是否存在 server 参数所代表的 Transporter 拓展,不存在则抛出异常 (代码直白,略)

        2. 创建服务器实例 (Exchangers.bind

          1. Exchangers.bindHeaderExchanger.bind,包含三层逻辑

            1. new HeaderExchangeHandler(handler)

            2. new DecodeHandler(new HeaderExchangeHandler(handler))

            3. Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))

              1. Transporters.getTransporter是动态创建的,类名为TransporterAdaptive,即自适应拓展类,根据传入的URL参数决定加载什么类型的Transporter,默认为NettyTransporter。 (NettyTransporter.bindNettyServerAbstractServerNettyServer.doOpen
        3. 检测是否支持 client 参数所表示的 Transporter 拓展,不存在也是抛出异常 (代码直白,略)

  2. 向注册中心注册服务 (RegistryProtocol.register

    1. 获取注册中心实例 (AbstractRegistryFactory.getRegistry

      1. 访问缓存,未命中则调用createRegistry创建Registry

        1. ZookeeperRegistryFactory.createRegistryZookeeperRegistry

          1. 获取组名,默认为dubbo

          2. 创建Zookeeper客户端,默认为CuratorZookeeperTransporterZookeeperTransporter.connect,此处文档和最新源码有出入,CuratorZookeeperTransporter.connect不存在,但CuratorZookeeperTransporter.createZookeeperClientCuratorZookeeperClient

            1. CuratorZookeeperClient构造方法主要用于创建和启动CuratorFramework实例,大部分为Curator框架的代码,可参考Curator官方文档
          3. 添加状态监听器

      2. 写入缓存

    2. 向注册中心注册服务 (将服务配置数据写入到Zookeeper的某个路径的节点下)

      1. FailbackRegistry.registryZookeeperRegistry.doRegistry

        1. toUrlPath方法生成节点路径

        2. AbstractZookeeperClient.create

          1. 通过递归创建当前节点的上一个路径

          2. 根据ephemeral的值决定创建临时节点还是持久节点 (createEphemeralcreatePersistent

            1. 只需分析其中一个,以createEphemeral为例

            2. CuratorZookeeperClient.createEphemeral,通过Curator框架创建节点

  3. 向注册中心进行订阅 override 数据 (非本章重点)

  4. 创建并返回 DestroyableExporter (无难度,略)

你可能感兴趣的:(看看Dubbo源码)