前言
前面用了 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
的方法)。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2cscXVE10dFRlY15kMMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL0UDO4ATNxAjM5ADNwAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
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);
通過打斷點,我們可以看出,當執行到上面那一行代碼是,我們傳入的參數如下圖所示:
wrapperInvoker 裡面包含了 invoker 對象,而 invoker 對象又包含了 url 對象,我們從 url 裡面可以取出 protocol 的值為 register,是以我們最終獲得的是
RegisterProtocol
,是以我們就可以接着把斷點打到
RegisterProtocol#export()
方法上。
當然,實際的過程更複雜,上面的接口還有對應的 Wrapper 包裝類,這又涉及到 Dubbo SPI 的知識點,是以當調用
Protocol
時,會産生如下的調用順序:
PROTOCOL#export()
- Protocol$Adaptive#export()
- ProtocolFilterWrapper#export()
- ProtocolListenerWrapper#export()
- 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 小結可知,這裡又會出現一條如下的調用鍊:
- Protocol$Adaptive
- ProtocolFilterWrapper
- ProtocolListenerWrapper
- DubboProtocol
3.3 DubboProtocol
到了這裡,遠端服務暴露流程先告一段落,
openServer()
會去開啟一個 NettyServer(這裡預設通信架構是 Netty),去建立連接配接,接收消息。 這些内容後面的文章會詳細講解。
總結
本文講解了遠端服務暴露的大緻的流程, 希望達成的目的如下:
- 了解整個大的流程架構會經過哪些核心環節,
- 解決整個流程執行過程的疑惑(或者說如何打斷點),
比如上面提到的為什麼會從
ServiceConfig
跳到
RegisterProtocol#export
方法, 歸根到底還是基于 Dubbo SPI 機制, 同時也證明了 Dubbo SPI 是研究 Dubbo 原理的基石。