1. 程式人生 > >用隧道協議實現不同dubbo叢集間的透明通訊

用隧道協議實現不同dubbo叢集間的透明通訊

# 用隧道協議實現不同dubbo叢集間的透明通訊 ## 前言 筆者最近完成了一個非常有意思的隧道機制(已在產線執行),可以讓註冊到不同zookeeper之間的dubbo叢集之間能夠正常進行通訊。如下圖所示: ![](https://oscimg.oschina.net/oscnet/up-9fa40bd54c6c29993cbf7fa2a23380313a3.png) 例如圖中A/B兩個網路隔離的叢集,兩者只能通過專線進行通訊。但是對於在裡面的應用來說,呼叫另外一個叢集的dubbo服務(例如app1呼叫app3)依舊和原來的方式一模一樣,無需做任何修改。這個特性對於新建單元(機房),業務網路隔離等場景非常有用。 本文就稍稍聊一下這個機制。 ## 場景 這個dubbo叢集通訊機制,可被用在下面的場景中。 ### 新建機房 在我們新建一個機房的過程中。正常情況下,需要將一整條鏈路的所有應用以及相關設施全部部署到新的機房中。如下圖所示: ![](https://oscimg.oschina.net/oscnet/up-3f53787bb885350170c9befeaa9cf463a9e.png) 而在筆者新的機制中,如果本叢集沒有對應的介面,會去尋找有對應介面的叢集,就算其中缺失了一些系統,整個機房依舊能夠work,將新建機房變為可迭代式的。大幅度減少了新建機房的複雜性。 ### 新建業務單元 由於單機房機架位的限制或者一些其它原因,有一些業務希望剝離到一個單獨的單元(機房裡面)。但是業務確需要一大堆原來單元的基礎服務。而不同單元之間的網路又無法打通(安全性要求)。 ![](https://oscimg.oschina.net/oscnet/up-851a47ffaf07f46341be5b724af32f211f6.png) 如果按照傳統的模式,勢必要對業務系統做改造,例如建立一個業務閘道器來負責和基礎系統的通訊,這個閘道器明顯費時費力而且沒什麼技術含量,例如在業務程式碼中將dubbo呼叫強行轉換為對業務閘道器的http呼叫,如下圖所示: ![](https://oscimg.oschina.net/oscnet/up-716da0ce3117a6d11aacf5175cb520b0ad4.png) 而且,每增加一個介面呼叫,都得在業務閘道器中轉換一把,新增對應的介面包,然後釋出。這樣的閘道器維護起來肯定是個天坑!隨著日益嚴格的安全性要求,不同業務間的網路隔離要求會與日俱增。 筆者是搞中介軟體的,堅信做的基礎服務能夠對業務透明,讓其感知不到才是一個好的設計。一旦需要業務大量配合這種由基礎架構變更而引起的改造,無疑是非常的不友好,甚至是個失敗的設計。 ### 故障隔離 事實上,筆者搞這一套隧道機制的初衷還有很大一部分原因是故障隔離。例如,筆者遇到數次由於業務系統使用zookeeper不當,往zookeeper寫了一大堆資料,從而讓整個叢集陷入不可用的風險。而新的機制,可以讓不同的業務註冊到不同的zookeeper,zookeeper掛了,也只是這個業務宕了,其它業務則不受影響。 ![](https://oscimg.oschina.net/oscnet/up-50abc1902a2314f52d8e9baf060f8270f3d.png) 事實上不僅為zookeeper,由於筆者對訊息(例如activemq)也做了這一套類似的隧道機制。使得我們的整個業務能夠更好的進行故障隔離! ## 隧道機制 筆者這個機制的最大便利性在於對業務的侵入性很少。對於基礎叢集的應用甚至完全不需要做修改。為了達成這個需求,筆者引入了在網路上非常常用的隧道概念(Tunnel),這個大家可能平時都接觸過,VPN/Vxlan這些網路協議都用了隧道。 ### 隧道穿透 我們先來看一下最基本的原理,在系統A通過Dubbo呼叫系統B的時候,在同一個叢集中走的是dubbo協議。而跨叢集的時候,筆者將dubbo原始位元流承載在http協議上,在專線上發出去。 ![](https://oscimg.oschina.net/oscnet/up-15f799ec9f125fc9ef90747efec3aabfabd.png) 由於在B系統看來,接收到的都是相同的byte流,其無法(也不用)區分到底是走了一層專線還是直接呼叫。所以B系統無需更改任何程式碼。 ### 隧道實現 那麼,這個隧道具體是如何實現,系統A又是如何知道需要本叢集沒有對應的介面,需要通過http隧道呼叫到另一個叢集的呢?這就引入了我們的隧道閘道器。 ![](https://oscimg.oschina.net/oscnet/up-abb6803cad1a1af6052cffaf9168c2e3e78.png) 這裡的概念也是和網路上的預設閘道器類似,如果本叢集內找不到對應的接受者就投遞到一個預設的閘道器,由這個隧道閘道器來替我們傳遞呼叫。 ## 如何發現這個閘道器 為了充分利用dubbo介面的註冊發現機制,筆者將隧道閘道器也暴露為一個dubbo介面,其輸入和輸出分別如下所示: ``` // 隧道閘道器介面請求體 class TunnelInterfaceReq { // dubbo元資訊,例如具體呼叫介面資訊 MetaData dubbo // 原始請求A呼叫序列化後的位元流 byte[] body; } // 隧道閘道器介面返回體 class TunnelInterfaceResp{ // dubbo元資訊 MetaData dubbo // 返回值呼叫序列化後的位元流,由另一個叢集的對應系統返回 byte[] resp; } ``` 有了這個dubbo介面,我們就可以很容易的將資料傳送給預設網關了。 ![](https://oscimg.oschina.net/oscnet/up-0ff978a96c4cd51e4c62bef00fc69d634b8.png) 注意,這裡其實也是做了一層隧道協議,即用dubbo協議承載dubbo協議,用這種類似套娃的方法有效的利用了dubbo本身的註冊發現機制。 ## 閘道器和閘道器之間通過http通訊 由於不同叢集之間通過專線進行通訊,所以筆者採用了http通訊來進行。在App1的請求到達隧道閘道器後,閘道器會將原始body位元流從TunnelInterfaceRequest中取出。然後放到一個http的請求中進行傳遞。如下圖所示: ![](https://oscimg.oschina.net/oscnet/up-cabade2057db213235ed82ec41f121a4340.png) 值得注意的是,由於傳遞的是byte流,沒有攜帶任何業務資訊(例如型別資訊等),所以我們的隧道閘道器可以對任意dubbo請求進行隧道傳輸,而不像傳統的閘道器那樣需要新增各種業務對應的jar包並不停釋出-_-! 在圖中,投遞到另一端的隧道閘道器後,其從http協議中取出呼叫元資訊和原始呼叫byte流,通過呼叫元資訊找到App2。然後給App2重放byte流,這樣就可以進行dubbo呼叫了。事實上,App2從隧道閘道器看到的byte流和從叢集內其它機器呼叫的byte流完全一致。如下圖所示: ![](https://oscimg.oschina.net/oscnet/up-a8298834488a3aa2b23a17164ef5d824937.png) ## 返回值也通過隧道機制 很明顯的,我們的返回值也需要通過隧道機制。和Request一樣,其也會走兩次隧道協議,如下圖所示: ![](https://oscimg.oschina.net/oscnet/up-6685bc03fe95fa05b091f98c6307d39d4e1.png) 那麼App1真正接收到的其實是Tunnel Response,怎麼讓其透明的接收原始response位元流呢?這就需要呼叫方接入筆者研發的輕量級jar包(其實,一開始的request的隧道也需要這樣的jar包) ## 對dubbo進行擴充套件 由於dubbo有非常優秀的filter機制,可以在各種地方可以擴充套件。為了這個隧道機制,筆者就擴充套件了其中的invoke呼叫邏輯。如下圖所示: ![](https://oscimg.oschina.net/oscnet/up-41510c67b959852a94b425c6027378a9181.png) 只要引入筆者寫的jar包,就能夠非常輕鬆的進行自動擴充套件,除了pom.xml加兩行,其它業務程式碼完全無需修改。 ## 隧道閘道器的介面發現 那麼隧道閘道器A是怎麼知道介面在叢集B,從而投遞給隧道閘道器B的呢?很明顯的,我們需要隧道閘道器間的叢集通訊機制。 ![](https://oscimg.oschina.net/oscnet/up-567bb7e9def7ece05dd4deccc3636a50d89.png) 例如,由隧道閘道器向其它不同的隧道閘道器詢問是否有此介面,並按一定策略做快取即可。 ![](https://oscimg.oschina.net/oscnet/up-01bb7a0d67064695b1ee07a7f390db769a1.png) ## dubbo叢集的發現 最後的問題就是隧道閘道器怎麼知道其它的dubbo叢集的了,由於相對於dubbo介面數量,叢集的數量是很少且不經常改變。我們只需要找個地方簡單的記錄下即可,例如放到資料庫裡面。然後由於是http呼叫,直接通過DNS解析域名即可做負載均衡。 ![](https://oscimg.oschina.net/oscnet/up-fb5867cd314661da35e5f3224263ff80184.png) ## 效能 由於筆者的這套機制序列化和反序列化完全在Provider/Consumer端,完全沒有對閘道器形成任何壓力,所以閘道器的CPU消耗很低。在單個呼叫延遲上,由於多了兩跳,不可避免的有所損耗,大概每個介面多了2ms左右。 ## 總結 這套機制從一開始構想,到完全能夠在產線執行,並且效能損耗還很小,筆者還是花費了不少的精力。看到這樣的結果,還是非常有成就感的。事實上,這套隧道機制在非常多的地方借鑑了網路上的概念。可謂它山之石可以攻玉!不同技術之間確實可以相互遷移,他們只是在不同的層級上解決了本質相通的問題! 歡迎大家關注我公眾號,裡面有各種乾貨,還有大禮包相送哦! ![](https://oscimg.oschina.net/oscnet/up-0124e4cdd8e9cecb13071dad7b6544ebb71.png)