天天看點

Dubbo 源碼分析 - Dubbo 遠端服務暴露流程(一)

前言

前面用了 7 篇文章 循序漸進 講解了 Dubbo SPI 的使用方法與應用,為今後分析 Dubbo 源碼與執行流程 奠定了基礎。接下來會通過 3-4 篇文章講解 Dubbo 服務遠端暴露流程。

先來看下閱讀本文的基礎:

  • 對 Dubbo SPI 有一定的了解
  • 對 Dubbo 的使用有所了解

再看幾個說明

  • 本文内容全部基于 Dubbo 2.7.x 的源碼
  • 文章内容是通過 debug 的調試源碼的方式提煉核心步驟, 直接定位到重點
  • 建議按照文章說明用 debug 跟蹤下源碼

正文

Dubbo 整個服務暴露流程很複雜,要經過很多類 和 方法,并且大部分方法還很長,我們肯定不能一行一行代碼去剖析。

是以本文的目的是把整個流程中 最核心的步驟 單獨拿出來,做一個簡易的時序圖, 是以分析的更加 粗略。

1. 服務暴漏入口

Dubbo 遠端服務暴露的 隐藏入口 是

ServiceConfig#export()

方法, 不管你是用 API 還是 Spring 的方式去啟動服務。

如下為 整個方法的源碼,我們上面有提到我們需要從冗長的方法中 提取 核心步驟,而我們下面的核心步驟就是

doExport()

方法

public synchronized void export() {
        // 是否需要暴露
        if (!shouldExport()) {
            return;
        }

        if (bootstrap == null) {
            bootstrap = DubboBootstrap.getInstance();
            bootstrap.init();
        }

        checkAndUpdateSubConfigs();

        //init serviceMetadata
        serviceMetadata.setVersion(version);
        serviceMetadata.setGroup(group);
        serviceMetadata.setDefaultGroup(group);
        serviceMetadata.setServiceType(getInterfaceClass());
        serviceMetadata.setServiceInterfaceName(getInterface());
        serviceMetadata.setTarget(getRef());
		
		// 是否延遲暴露
        if (shouldDelay()) {
            DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
        } else {
            doExport();
        }

        exported();
}
           

再次強調: 很明顯上面有很長一段代碼, 但我們關注的重點就隻有

doExport

方法, 其他的暫時不用管, 後面的文章會補充.

2. 核心流程

運作 Dubbo 官方 demo(或者源碼中的測試用例等等),目的是将服務注冊到 zookeeper 上(你也可以用其他注冊中心),然後通過 debug 模式運作代碼,初始斷點就打在

ServiceConfig#export()

方法上,然後一步一步跟下去,你就能梳理出下面的時序圖(至少可以跟蹤完圖中

ServiceConfig

的方法)。

Dubbo 源碼分析 - Dubbo 遠端服務暴露流程(一)

3. 源碼分析

上面的時序圖中涉及到 4 個類,我們一個一個來看。

3.1 ServiceConfig

通過 debug 從初始斷點

ServiceConfig#export()

一步一步往下跟蹤,前 5 步都是肉眼可見的,具體代碼就不貼了, 但是這裡需要講一下,為什麼第 5 步調用了

PROTOCOL.export()

方法就突然跳到

RegisterProtocol#export()

方法。

來看一下

PROTOCOL

的相關定義:
# 去除了一些修飾符
Protocol PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
           

而 Protocol 是一個擴充類,這個前面的文章中詳細舉例說明過。

@SPI("dubbo")
public interface Protocol {

    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
}
           
綜上所得,第 5 步用到了 Dubbo SPI 的自适應擴充機制。

根據我們之前講的可以得知:在調用

export()

方法時,會動态生成一個

Protocol$Adaptive

, 然後根據傳入的參數(要麼是URL,要麼是URL的包裝類),取出對應的 key,比如 key 對應的值為 dubbo,那麼獲得擴充類就是

DubboProtocol

,如果值是 register,那麼獲得的自适應擴充類就是

RegisterProtocol

那麼我們來看下傳入的參數是什麼:
Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
           

通過打斷點,我們可以看出,當執行到上面那一行代碼是,我們傳入的參數如下圖所示:

Dubbo 源碼分析 - Dubbo 遠端服務暴露流程(一)

wrapperInvoker 裡面包含了 invoker 對象,而 invoker 對象又包含了 url 對象,我們從 url 裡面可以取出 protocol 的值為 register,是以我們最終獲得的是

RegisterProtocol

,是以我們就可以接着把斷點打到

RegisterProtocol#export()

方法上。

當然,實際的過程更複雜,上面的

Protocol

接口還有對應的 Wrapper 包裝類,這又涉及到 Dubbo SPI 的知識點,是以當調用

PROTOCOL#export()

時,會産生如下的調用順序:
  1. Protocol$Adaptive#export()
  2. ProtocolFilterWrapper#export()
  3. ProtocolListenerWrapper#export()
  4. RegistryProtocol#export()

這些之前關于 Dubbo SPI 的文章是有說明過的,這裡再次提一下。

3.2 RegisterProtocol

緊接着上一步,我們來到了

RegisterProtocol#export()

方法,該方法很長,我們直接找到下面一行代碼:

//export invoker
        final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);
           

該方法的具體代碼如下:

private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {
        String key = getCacheKey(originInvoker);

        return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> {
            Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);
			
			// 重點看這裡 protocol.export(invokerDelegate)
            return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker);
        });
    }
           

我們從上面提煉出核心步驟

protocol.export(invokerDelegate)

, 從 3.1 小結可知,這裡又會出現一條如下的調用鍊:

  1. Protocol$Adaptive
  2. ProtocolFilterWrapper
  3. ProtocolListenerWrapper
  4. DubboProtocol

3.3 DubboProtocol

到了這裡,遠端服務暴露流程先告一段落,

openServer()

會去開啟一個 NettyServer(這裡預設通信架構是 Netty),去建立連接配接,接收消息。 這些内容後面的文章會詳細講解。

總結

本文講解了遠端服務暴露的大緻的流程, 希望達成的目的如下:

  1. 了解整個大的流程架構會經過哪些核心環節,
  2. 解決整個流程執行過程的疑惑(或者說如何打斷點),

比如上面提到的為什麼會從

ServiceConfig

跳到

RegisterProtocol#export

方法, 歸根到底還是基于 Dubbo SPI 機制, 同時也證明了 Dubbo SPI 是研究 Dubbo 原理的基石。