點贊再看,養成習慣,微信搜一搜【三太子敖丙】關注這個喜歡寫情懷的程式員,
本文 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®istry=zookeeper×tamp=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,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/97426.html
標籤:Java
上一篇:delphi TDecompressionStream 壓縮資料無法用c++ zlib解壓
下一篇:呼叫webservice報錯
