主頁 > 後端開發 > Dubbo系列之服務暴露程序

Dubbo系列之服務暴露程序

2020-09-21 13:55:56 後端開發

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

本文 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,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/97426.html

標籤:Java

上一篇:delphi TDecompressionStream 壓縮資料無法用c++ zlib解壓

下一篇:呼叫webservice報錯

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more