8、dubbo源碼分析 之 服務(wù)遠程暴露(上)

在上一篇文章我們講解了一下 dubbo 服務(wù)暴露過程中的本地暴露。它只是一個開胃小菜,主要是為我們后面講解遠程暴露開個頭。下面就來分析一下 dubbo 在遠程暴露里面發(fā)生了哪些事。因為 dubbo 遠程暴露里面的過程還是比較復(fù)雜的,所以我就分為三個文章來講解 dubbo 的遠程暴露:

  1. dubbo 遠程暴露 -- Netty 暴露服務(wù)
  2. dubbo 遠程暴露 -- Zookeeper 連接
  3. 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到不同的注冊中心上去。

  1. 把配置的信息通過appendParameters提取到 map 中
  2. 判斷是否支持泛化調(diào)用
  3. 通過協(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 ...)
  4. 循環(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 對象如下圖所示。因為 ProtocolFilterWrapperProtocolFilterWrapper是過濾 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ù)。

  1. 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 ...,然后再刪除 dynamicenabled 參數(shù)
  2. Map<String, ExporterChangeableWrapper<?>> bounds緩存中根據(jù)上面獲取的 key 獲取 Exporter 對象,如果獲取到直接返回;否則進行服務(wù)暴露
  3. 通過 originInvoker獲取里面的 URL 獲取到協(xié)議的統(tǒng)一模型以及originInvoker本身創(chuàng)建 InvokerDelegete
  4. 根據(jù)InvokerDelegete暴露服務(wù),因為 URL 協(xié)議是 dubbo,所以獲取到的實例是 DubboProtocol,而這個對象因為協(xié)議不是 registry,所以生成ProtocolListenerWrapper會根據(jù) SPI 機制檢測 dubbo 里面配置的 InvokerListener 擴展;而 ProtocolFilterWrapper 會根據(jù) SPI機制檢測 dubbo里面配置的 Filter 擴展。所以最終通過 DubboProtocol來處理暴露過程。
    這里寫圖片描述
  5. 暴露生成的 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;
    }

這斷代碼主要的操作是:

  1. 根據(jù)傳入的Invoker中的 URL 通過serviceKey(url)獲取到 serviceKey,它的格式為:com.alibaba.dubbo.demo.DemoService:20880.
  2. 以傳的Invoker、第 1 步生成的 key 和 Map<String, Exporter<?>> exporterMap 生成 DubboExporter,并以第 1 步生成的 key 為索引,把生成的 DubboExporter添加到Map<String, Exporter<?>> exporterMap
  3. 根據(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 的遠程通信。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容