Dubbo系列之服務暴露過程

點贊再看,養成習慣,微信搜一搜【三太子敖丙】關注這個喜歡寫情懷的程序員。

本文 GitHub https://github.com/JavaFamily 已收錄,有一線大廠面試完整考點、資料以及我的系列文章。

這周去蘇州見大佬,沒想到遇到一堆女粉絲,其中居然還有澡堂子堂妹,堂妹一遇到我就說敖丙哥哥我超級喜歡你寫的dubbo系列,你能跟我好好講一下他的服務暴露過程麼?

我笑了笑:傻瓜,你想看怎麼不早點說呢?

我今天來就帶大家看看 Dubbo 服務暴露過程,這個過程在 Dubbo 中其實是很核心的過程之一,關乎到你的 Provider 如何能被 Consumer 得知並調用。

今天還是會進行源碼解析,畢竟我們需要深入的去了解 Dubbo 是如何做的,只有深入它才能瞭解它。

不用擔心源碼問題,因爲不僅僅有源碼解析,敖丙也會通過畫圖和總結性的語言幫助大家理解,而且在面對面試官的時候,總結性的語言纔是最重要的,因爲不見得面試官也懂得或者記得具體的細節。

對了,源碼是 2.6.5 版本。

URL

不過在進行服務暴露流程分析之前有必要先談一談 URL,有人說這 URL 和 Dubbo 啥關係?有關係,有很大的關係!

一般而言我們說的 URL 指的就是統一資源定位符,在網絡上一般指代地址,本質上看其實就是一串包含特殊格式的字符串,標準格式如下:

protocol://username:password@host:port/path?key=value&key=value

Dubbo 就是採用 URL 的方式來作爲約定的參數類型,被稱爲公共契約,就是我們都通過 URL 來交互,來交流。

你想一下如果沒有一個約束,沒有指定一個都公共的契約那麼不同的接口就會以不同的參數來傳遞信息,一會兒用 Map、一會兒用特定分隔的字符串,這就是導致整體很亂,並且解析不能統一。

用了一個統一的契約之後,那麼代碼就更加的規範化、形成一種統一的格式,所有人對參數就一目瞭然,不用去揣測一些參數的格式等等。

而且用 URL 作爲一個公共約束充分的利用了我們對已有概念的印象,通俗易懂並且容易擴展,我們知道 URL 要加參數只管往後面拼接就完事兒了。

因此 Dubbo 用 URL 作爲配置總線,貫穿整個體系,源碼中 URL 的身影無處不在。

URL 具體的參數如下:

  • protocol:指的是 dubbo 中的各種協議,如:dubbo thrift http
  • username/password:用戶名/密碼
  • host/port:主機/端口
  • path:接口的名稱
  • parameters:參數鍵值對

配置解析

一般常用 XML 或者註解來進行 Dubbo 的配置,我稍微說一下 XML 的,這塊其實是屬於 Spring 的內容,我不做過多的分析,就稍微講一下大概的原理。

Dubbo 利用了 Spring 配置文件擴展了自定義的解析,像 dubbo.xsd 就是用來約束 XML 配置時候的標籤和對應的屬性用的,然後 Spring 在解析到自定義的標籤的時候會查找 spring.schemas 和 spring.handlers。

spring.schemas 就是指明瞭約束文件的路徑,而 spring.handlers 指明瞭利用該 handler 來解析標籤,你看好的框架都是會預留擴展點的,講白了就是去固定路徑的固定文件名去找你擴展的東西,這樣才能讓用戶靈活的使用。

我們再來看一下 DubboNamespaceHandler 都幹了啥。

講白了就是將標籤對應的解析類關聯起來,這樣在解析到標籤的時候就知道委託給對應的解析類解析,本質就是爲了生成 Spring 的 BeanDefinition,然後利用 Spring 最終創建對應的對象。

服務暴露全流程

我們在深入源碼之前來看下總的流程,有個大致的印象看起來比較不容易暈

代碼的流程來看大致可以分爲三個步驟(本文默認都需要暴露服務到註冊中心)。

第一步是檢測配置,如果有些配置空的話會默認創建,並且組裝成 URL 。

第二步是暴露服務,包括暴露到本地的服務和遠程的服務。

第三步是註冊服務至註冊中心。

對象構建轉換的角度看可以分爲兩個步驟。

第一步是將服務實現類轉成 Invoker。

第二部是將 Invoker 通過具體的協議轉換成 Exporter。

服務暴露源碼分析

接下來我們進入源碼分析階段,從上面配置解析的截圖標紅了的地方可以看到 service 標籤其實就是對應 ServiceBean,我們看下它的定義。

這裏又涉及到 Spring 相關內容了,可以看到它實現了 ApplicationListener<ContextRefreshedEvent>,這樣就會在 Spring IOC 容器刷新完成後調用 onApplicationEvent 方法,而這個方法裏面做的就是服務暴露,這就是服務暴露的啓動點。

可以看到,如果不是延遲暴露、並且還沒暴露過、並且支持暴露的話就執行 export 方法,而 export 最終會調用父類的 export 方法,我們來看看。

主要就是檢查了一下配置,確認需要暴露的話就暴露服務, doExport 這個方法很長,不過都是一些檢測配置的過程,雖說不可或缺不過不是我們關注的重點,我們重點關注裏面的 doExportUrls 方法。

可以看到 Dubbo 支持多註冊中心,並且支持多個協議,一個服務如果有多個協議那麼就都需要暴露,比如同時支持 dubbo 協議和 hessian 協議,那麼需要將這個服務用兩種協議分別向多個註冊中心(如果有多個的話)暴露註冊。

loadRegistries 方法我就不做分析了,就是根據配置組裝成註冊中心相關的 URL ,我就給大家看下拼接成的 URL的樣子。

registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&pid=7960&qos.port=22222&registry=zookeeper&timestamp=1598624821286

我們接下來關注的重點在 doExportUrlsFor1Protocol 方法中,這個方法挺長的,我會截取大致的部分來展示核心的步驟。

此時構建出來的 URL 長這樣,可以看到走得是 dubbo 協議。

然後就是要根據 URL 來進行服務暴露了,我們再來看下代碼,這段代碼我就直接截圖了,因爲需要斷點的解釋。

本地暴露

我們再來看一下 exportLocal 方法,這個方法是本地暴露,走的是 injvm 協議,可以看到它搞了個新的 URL 修改了協議。

我們來看一下這個 URL,可以看到協議已經變成了 injvm。

這裏的 export 其實就涉及到上一篇文章講的自適應擴展了。

 Exporter<?> exporter = protocol.export(
                    proxyFactory.getInvoker(ref, (Class) interfaceClass, local));

Protocol 的 export 方法是標註了 @ Adaptive 註解的,因此會生成代理類,然後代理類會根據 Invoker 裏面的 URL 參數得知具體的協議,然後通過 Dubbo SPI 機制選擇對應的實現類進行 export,而這個方法就會調用 InjvmProtocol#export 方法。

我們再來看看轉換得到的 export 到底長什麼樣子。

從圖中可以看到實際上就是具體實現類層層封裝, invoker 其實是由 Javassist 創建的,具體創建過程 proxyFactory.getInvoker 就不做分析了,對 Javassist 有興趣的同學自行去了解,之後可能會寫一篇,至於 dubbo 爲什麼用 javassist 而不用 jdk 動態代理是因爲 javassist 快

爲什麼要封裝成 invoker

至於爲什麼要封裝成 invoker 其實就是想屏蔽調用的細節,統一暴露出一個可執行體,這樣調用者簡單的使用它,向它發起 invoke 調用,它有可能是一個本地的實現,也可能是一個遠程的實現,也可能一個集羣實現。

爲什麼要搞個本地暴露呢

因爲可能存在同一個 JVM 內部引用自身服務的情況,因此暴露的本地服務在內部調用的時候可以直接消費同一個 JVM 的服務避免了網絡間的通信

可以有些同學已經有點暈,沒事我這裏立馬搞個圖帶大家過一遍。

對 exportLocal 再來一波時序圖分析。

遠程暴露

至此本地暴露已經好了,接下來就是遠程暴露了,即下面這一部分代碼

也和本地暴露一樣,需要封裝成 Invoker ,不過這裏相對而言比較複雜一些,我們先來看下 registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()) 將 URL 拼接成什麼樣子。

registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&export=dubbo://192.168.1.17:20880/com.alibaba.dubbo.demo.DemoService....

因爲很長,我就不截全了,可以看到走 registry 協議,然後參數裏又有 export=dubbo://,這個走 dubbo 協議,所以我們可以得知會先通過 registry 協議找到 RegistryProtocol 進行 export,並且在此方法裏面還會根據 export 字段得到值然後執行 DubboProtocol 的 export 方法。

大家要挺住,就快要完成整個流程的解析了!

現在我們把目光聚焦到 RegistryProtocol#export 方法上,我們先過一遍整體的流程,然後再進入 doLocalExport 的解析。

可以看到這一步主要是將上面的 export=dubbo://... 先轉換成 exporter ,然後獲取註冊中心的相關配置,如果需要註冊則向註冊中心註冊,並且在 ProviderConsumerRegTable 這個表格中記錄服務提供者,其實就是往一個 ConcurrentHashMap 中將塞入 invoker,key 就是服務接口全限定名,value 是一個 set,set 裏面會存包裝過的 invoker 。

我們再把目光聚焦到 doLocalExport 方法內部。

這個方法沒什麼難度,主要就是根據URL上 Dubbo 協議暴露出 exporter,接下來就看下 DubboProtocol#export 方法。

可以看到這裏的關鍵其實就是打開 Server ,RPC 肯定需要遠程調用,這裏我們用的是 NettyServer 來監聽服務。

再下面我就不跟了,我總結一下 Dubbo 協議的 export 主要就是根據 URL 構建出 key(例如有分組、接口名端口等等),然後 key 和 invoker 關聯,關聯之後存儲到 DubboProtocol 的 exporterMap 中,然後如果是服務初次暴露則會創建監聽服務器,默認是 NettyServer,並且會初始化各種 Handler 比如心跳啊、編解碼等等。

看起來好像流程結束了?並沒有, Filter 到現在還沒出現呢?有隱藏的措施,上一篇 Dubbo SPI 看的仔細的各位就知道在哪裏觸發的。

其實上面的 protocol 是個代理類,在內部會通過 SPI 機制找到具體的實現類。

這張圖是上一篇文章的,可以看到 export 具體的實現。

複習下上一篇的要點,通過 Dubbo SPI 掃包會把 wrapper 結尾的類緩存起來,然後當加載具體實現類的時候會包裝實現類,來實現 Dubbo 的 AOP,我們看到 DubboProtocol 有什麼包裝類。

可以看到有兩個,分別是 ProtocolFilterWrapper 和 ProtocolListenerWrapper

對於所有的 Protocol 實現類來說就是這麼個調用鏈。

而在 ProtocolFilterWrapper 的 export 裏面就會把 invoker 組裝上各種 Filter。

看看有 8 個在。

我們再來看下 zookeeper 裏面現在是怎麼樣的,關注 dubbo 目錄。

兩個 service 佔用了兩個目錄,分別有 configurators 和 providers 文件夾,文件夾裏面記錄的就是 URL 的那一串,值是服務提供者 ip。

至此服務流程暴露差不多完結了,可以看到還是有點內容在裏面的,並且還需要掌握 Dubbo SPI,不然有些點例如自適應什麼的還是很難理解的。最後我再來一張完整的流程圖帶大家再過一遍,具體還是有很多細節,不過不是主幹我就不做分析了,不然文章就有點散。

然後再引用一下官網的時序圖。

總結

還是建議大家自己打斷點過一遍,這樣能夠更加的清晰,到時候面試官問起來一點都不虛,不過只要你認真看了這篇文章也差不多了,總的流程能說出來能證明你看過源碼,一些細節記不住的,你想想看你自己寫的代碼過一兩個月你記得住不?更別說別人寫的了。

其實我可以不源碼分析,我可以直接口述 + 畫圖,觀賞性更佳,但是爲什麼我還是貼代碼呢?

想帶着大家從源碼級別來過一遍流程,這樣能讓大家更有底氣,畢竟你看圖理解了是一回事,真正的看到源碼,就會很直觀的知道一些點,例如,緩存原來就是放一個 map 中,這過濾鏈原來是這樣拼接的等等等等。

總的而言服務暴露的過程起始於 Spring IOC 容器刷新完成之時,具體的流程就是根據配置得到 URL,再利用 Dubbo SPI 機制根據 URL 的參數選擇對應的實現類,實現擴展。

通過 javassist 動態封裝 ref (你寫的服務實現類),統一暴露出 Invoker 使得調用方便,屏蔽底層實現細節,然後封裝成 exporter 存儲起來,等待消費者的調用,並且會將 URL 註冊到註冊中心,使得消費者可以獲取服務提供者的信息。

今天這個就差不多了,Dubbo 系列估計還有幾篇,到時候再來個面試彙總,等着吧!

我是敖丙,你知道的越多,你不知道的越多,我們下期見!

人才們的 【三連】 就是敖丙創作的最大動力,如果本篇博客有任何錯誤和建議,歡迎人才們留言!


文章持續更新,可以微信搜一搜「 三太子敖丙 」第一時間閱讀,回覆【資料】有我準備的一線大廠面試資料和簡歷模板,本文 GitHub https://github.com/JavaFamily 已經收錄,有大廠面試完整考點,歡迎Star。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章