在上一篇文章我們講解了一下 dubbo 服務(wù)暴露過程中的本地暴露。它只是一個開胃小菜,主要是為我們后面講解遠程暴露開個頭。下面就來分析一下 dubbo 在遠程暴露里面發(fā)生了哪些事。因為 dubbo 遠程暴露里面的過程還是比較復(fù)雜的,所以我就分為三個文章來講解 dubbo 的遠程暴露:
- dubbo 遠程暴露 -- Netty 暴露服務(wù)
- dubbo 遠程暴露 -- Zookeeper 連接
- dubbo 遠程暴露 -- Zookeeper 注冊 & 訂閱
這就篇就是分析 dubbo 服務(wù)暴露中通過 Netty 來暴露服務(wù)(當然 dubbo 還可以通過 Mina、Grizzly 來暴露服務(wù),默認使用 Netty)。
1、ServiceConfig#doExportUrls
首先通過方法loadRegistries(true)
來加載注冊中心。在方法checkRegistry()
方法中判斷如果 xml 里面沒有配置注解中心,從 dubbo 的 properties 文件中獲取(默認是dubbo.properties
)。然后會返回List<URL>
作為配置信息的統(tǒng)一格式,所有擴展點都通過傳遞 URL 攜帶配置信息。URL的格式如下:
registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.0 ...
因為 dubbo 支持多種協(xié)議,遍歷所有協(xié)議分別根據(jù)不同的協(xié)議把服務(wù)export到不同的注冊中心上去。
- 把配置的信息通過
appendParameters
提取到 map 中 - 判斷是否支持泛化調(diào)用
- 通過協(xié)議名稱、host、port、contextPath 和第一步提取出來的 map 構(gòu)造協(xié)議的統(tǒng)一數(shù)據(jù)模型 URL (如:
dubbo://169.254.69.197:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider ...
) - 循環(huán)遍注冊中心,把服務(wù)暴露在不同的注冊中心當中
a) 如果配置了 monitor,就返回監(jiān)控統(tǒng)一模型數(shù)據(jù) URL,并給以monitor
為 key 添加到生成的 URL中,URL格式如下:
registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?export=dubbo%3A%2F%2F169.254.69.197%3A20880%2Fcom.alibaba.dubbo.demo.DemoService& ...
b) 把協(xié)議統(tǒng)一模型 URL 以export
為 key,添加到注冊中心的統(tǒng)一模型 URL中
c) 根據(jù)服務(wù)的具體實現(xiàn)、實現(xiàn)的接口以及注冊中心統(tǒng)一模型 URL從代理工廠ProxyFactory
(SPI 默認獲取到 JavassistProxyFactory)獲取Invoker
對象。
這里寫圖片描述
d) 通過Protocol#export(invoker)
暴露服務(wù),因為注冊的協(xié)議是registry
所以生成的 Protocol 對象如下圖所示。因為ProtocolFilterWrapper
和ProtocolFilterWrapper
是過濾registry
協(xié)議的,所以最終通過RegistryProtocol
來處理暴露過程。
這里寫圖片描述
2、RegistryProtocol#export
根據(jù)這個類名我們就可以推測出這個類具有的功能,具有 Registry
(注冊)與 Protocol
(協(xié)議--服務(wù)暴露)在這個方法里面就包括上面提到的三個邏輯:
- dubbo 遠程暴露 -- Netty 暴露服務(wù),通過配置的協(xié)議根據(jù) SPI 獲取到對應(yīng)的
Protocol
對象,這里是 DubboProtocol,對象。 - dubbo 遠程暴露 -- Zookeeper 連接 服務(wù)注冊,通過
RegistryFactory
根據(jù) SPI 獲取對應(yīng)的Registry
對象(ZookeeperRegistry
),然后注冊到注冊中心上面去,供consumer
調(diào)用 - dubbo 遠程暴露 -- Zookeeper 注冊 & 訂閱,它會把創(chuàng)建2個節(jié)點:一個是
/dubbo/服務(wù)全類名/provider/...
節(jié)點提供給服務(wù)消費方查看節(jié)點信息;二是/dubbo/服務(wù)全類名/configurators/...
節(jié)點提供給服務(wù)方watch
(監(jiān)控) dubbo-admin 對于服務(wù)的修改。比如:服務(wù)權(quán)重。
上面粗略的講了一下服務(wù)遠程暴露主要干了哪些事,主要是想讓大家有一個全局的意識。下面我們就來講一下 dubbo 服務(wù)是如何通過 Netty 來暴露服務(wù)。
- getCacheKey(originInvoker),通過 Invoker 對象獲取到緩存 key,還記得我們在
ServiceConfig#doExportUrls
的 4-b 步驟里面嗎?它就是把保存在 注冊統(tǒng)一模型里面的export
key 獲取到協(xié)議的統(tǒng)一模型dubbo://169.254.69.197:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider ...
,然后再刪除dynamic
與enabled
參數(shù) - 從
Map<String, ExporterChangeableWrapper<?>> bounds
緩存中根據(jù)上面獲取的 key 獲取 Exporter 對象,如果獲取到直接返回;否則進行服務(wù)暴露 - 通過
originInvoker
獲取里面的 URL 獲取到協(xié)議的統(tǒng)一模型以及originInvoker
本身創(chuàng)建InvokerDelegete
。 - 根據(jù)
InvokerDelegete
暴露服務(wù),因為 URL 協(xié)議是dubbo
,所以獲取到的實例是DubboProtocol
,而這個對象因為協(xié)議不是registry
,所以生成ProtocolListenerWrapper
會根據(jù) SPI 機制檢測 dubbo 里面配置的 InvokerListener 擴展;而ProtocolFilterWrapper
會根據(jù) SPI機制檢測 dubbo里面配置的 Filter 擴展。所以最終通過DubboProtocol
來處理暴露過程。
這里寫圖片描述 - 暴露生成的 Exporter 和 傳入的
originInvoker
會創(chuàng)建ExporterChangeableWrapper
對象會以步驟 1 生成的 key 緩存在Map<String, ExporterChangeableWrapper<?>> bounds
當中,并返回結(jié)果。
3、DubboProtocol#export
整個DubboProtocol#export
的代碼如下:
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;
}
這斷代碼主要的操作是:
- 根據(jù)傳入的
Invoker
中的 URL 通過serviceKey(url)
獲取到 serviceKey,它的格式為:com.alibaba.dubbo.demo.DemoService:20880
. - 以傳的
Invoker
、第 1 步生成的 key 和Map<String, Exporter<?>> exporterMap
生成DubboExporter
,并以第 1 步生成的 key 為索引,把生成的DubboExporter
添加到Map<String, Exporter<?>> exporterMap
中 - 根據(jù) URL 判斷是不是服務(wù)端,如果是服務(wù)端并且從
Map<String, ExchangeServer> serverMap
獲取到的 ExchangeServer 為空,就通過DubboProtocol#createServer
創(chuàng)建服務(wù),達到服務(wù)暴露的目的。返回DubboExporter
對象
4、DubboProtocol#createServer
dubbo 遠程服務(wù)(Provider)暴露最終其實就是創(chuàng)建一個 Netty Serve 服務(wù),然后在 dubbo 在服務(wù)引用的時候創(chuàng)建一個 Netty Client 服務(wù)。其實 dubbo 遠程通信的原理其實就是基于 Socket 的遠程通信。下面我們來看一下 dubbo 是如何創(chuàng)建一個 Netty 服務(wù)的,下面就是它創(chuàng)建的序列圖:
它通過傳入 URL 與 requestHandler
來創(chuàng)建一個 ExchangeServer,通過Netty 基于 NIO的形式通過自定義Channel來接收服務(wù)引用方傳遞過來的信息,以及發(fā)送調(diào)用遠程服務(wù)的本地方法后的數(shù)據(jù)給服務(wù)調(diào)用者。URL 里面主要包含 IP 地址 與 端口信息用于創(chuàng)建 Socket 連接,而 requestHandler
是一個 ExchangeHandler 通過自定義協(xié)議來處理 dubbo 的遠程通信。