再來填個坑,聊一聊Dubbo應用級服務注冊的實現原理
大家好,我是三友~~
在之前寫的7000字+22張圖探秘Dubbo一次RPC調用的核心流程這篇閱讀量非常感人的文章中我留了一個小坑。
圖片
在發文之前我也猜到了這篇文章閱讀量大概率會很感人,所以壓根兒覺得不可能完成。但是結果是,閱讀量雖然如預期所料,但是點贊量卻著實給了個小驚喜。
圖片
既然現在已經滿38個了,那么我這就來把這個坑給填一填。講一講Dubbo在3.x版本之后為什么使用應用級服務注冊以及它背后的實現原理。
還是一個簡單的Demo
同樣地,為了保證文章的完整性和連貫性,防止你忘記了,我把之前那篇文章的Demo再拿過去。
如果你還記得,可直接跳過本節,直接進入下一節
在Dubbo中RPC調用過程中主要分為以下兩個角色:
- 服務提供者:提供一個接口給消費者遠程調用
- 服務消費者:調用生產者提供的接口
一個簡單的Dubbo示例工程就如下所示:
Demo中Dubbo使用的是3.0.7的版本,Nacos使用的是2.3.2版本,代碼地址 https://github.com/sanyou3/dubbo-demo.git接口層,提供者消費者都需要依賴,服務提供者實現,服務消費者調用;
圖片
服務提供者單獨一個工程,實現DemoService接口,通過@DubboService表明提供DemoService這個服務;
圖片
服務提供者配置文件;
圖片
服務消費者單獨一個工程,這里使用單元測試,通過@DubboReference注解表明消費DemoService這個服務接口;
圖片
服務消費者配置文件;
圖片
啟動服務提供者,運行消費者單元測試,結果如下:
成功實現遠程服務調用
應用級服務注冊和接口級服務注冊
1、應用級服務注冊
談到應用級服務注冊,其實我們都很了解
就比如說在SpringCloud環境下,服務實例在啟動的時候會將自身的服務名、IP、端口外加一些其它的數據注冊到注冊中心,但是在這一過程中并不會將服務的接口信息注冊到注冊中心。所以對于服務調用者(消費者)來說,它也只能從注冊中心獲取到服務名、IP和端口這些信息,無法獲取到服務提供者提供了哪些接口,這就是應用級服務注冊,如下圖所示
圖片
所以應用級服務注冊用一句話概括就是:
一個服務不管對外提供了多少接口,它都是作為一個整體注冊到服務注冊中心
2、接口級服務注冊
接口級服務注冊就跟應用級服務注冊相反了。接口級服務注冊就是把每個單獨的接口看成一個服務進行注冊。所以服務在啟動的時候,每個接口都將作為單獨的服務注冊到注冊中心。也就是說,有幾個接口,就有幾個服務,就注冊幾次。Dubbo在2.x版本的時候就是使用的接口級服務注冊。所以在前面的Demo中,你可以在注冊中心中看到如下接口級服務注冊的服務信息。
圖片
Dubbo3.x兼容2.x接口級服務注冊,所以也能看到
當然也包括接口的詳細信息;
圖片
消費者在訂閱的時候也就是訂閱所需要消費的接口對應的服務信息。所以接口級服務注冊用一句話概括就是。
以接口為單位進行服務注冊和發現,每個服務的每個接口都單獨注冊和發現
為什么Dubbo在3.x版本要使用應用級服務注冊
之所以Dubbo在3.x版本之后放棄使用接口級服務注冊,轉而使用應用級服務注冊,主要包括以下兩點原因:
第一點就是接口級服務注冊會增加注冊中心的壓力。
壓力主要來自三個部分:
- 服務注冊的壓力
- 服務變更通知的壓力
- 服務數據存儲的壓力
對于應用級服務來說,一個服務實例只需要注冊1次。但是對于接口級服務注冊來說,有多少接口就得注冊多少次,如果有100個,那么就得注冊100次。這就大大提高了注冊中心服務注冊的壓力。至于服務變更通知的壓力,這也很好理解。我們都知道,服務注冊中心一般都有一個服務數據變更通知的功能。當有服務實例注冊時,注冊中心會去通知訂閱了這個服務的其它服務,告訴其它服務所訂閱的服務實例數據有變更。
圖片
對于應用級服務來說,一個服務實例上線只需要給另一個訂閱了該服務的服務實例推送1次就可以了。但是對于接口級服務注冊來說,如果服務提供者有100個接口,服務消費者訂閱了這100個接口服務。那么一個服務實例上線,注冊中心需要給另一個服務消費者推送100次服務變更的消息。這就造成了注冊中心服務變更推送的壓力。至于第三個就更好理解了,注冊的服務數據變多了,那么存儲的壓力就會變大。所以這么一對比就可以清晰的得出一個結論
接口級服務注冊的壓力遠遠大于服務級注冊的壓力
這就是為什么要換成應用級服務注冊的第一個原因。至于第二個原因,就聽起來這就比較高大上了。主要是為了向SpringCloud和K8S等生態靠齊。因為SpringCloud和K8S它們其實都是應用級的注冊和發現。所以為了更好的融入SpringCloud和K8S的生態。Dubbo在3.x就開始轉向應用級的服務注冊和發現。如果非得換一句逼格高的措辭來表示,那就是對現代微服務架構和云原生技術趨勢的適應和支持。
Dubbo3.x應用級服務注冊的實現原理
前面鋪墊完了,接下來我們就來講一講Dubbo3.x應用級服務注冊的實現原理。雖然采用了應用級服務注冊,但是Dubbo的本質并沒有改變。依然還是使用接口來調用。所以對于消費者來說,還是必須得知道接口的詳情數據,包括接口所在服務器的IP、端口、通信協議等等。但是現在注冊中心只有應用級服務信息,并沒有接口級服務信息,怎么獲取呢?
Dubbo將整個實現總共拆為兩步:
- 消費者需要先獲取消費的接口所在的服務名
- 消費者通過獲取到服務名再去獲取接口詳情數據
1、接口是哪個服務提供的?
首先第一步,消費者需要先獲取消費的接口所在的服務名,那么問題來了。
消費者如何去獲取到消費的接口所在的服務名?
但是當你仔細思考一下時,你其實會發現這并不算是一個問題,因為很簡單,服務提供者和服務消費者一般都我們自己開發的服務,所以我們肯定知道接口在哪個服務上,就像OpenFeign一樣,我們每次使用時都會自己指明接口所在的服務。
圖片
所以Dubbo也給我們提供了3種配置方式,讓我們可以手動指定接口所在的服務。
第一種,使用@DubboReference#providedBy屬性配置。
圖片
第二種,通過消費者配置文件配置接口所在的服務名。
圖片
第三種,在消費者配置文件注冊中心的配置中加上接口所在的服務名
圖片
當你加了這些配置的時候,Dubbo就認為消費的接口就在這些服務中,雖然通過配置可以指定,但是我不知道你有沒有發現,前面演示的Demo中我并沒有進行任何配置,也能調用成功。所以除了這種配置的方式之外,Dubbo還提供了第二種方式,叫做服務名自動探測。服務提供者在啟動的時候,將接口全限定名以及當前服務名的映射關系存到一個中間的地方,而消費者只需要根據消費的接口到這個中間的地方就可以查到接口所在的服務名,這個中間的地方在Dubbo中被稱為元數據中心。
圖片
這里你肯定有一個疑問?
元數據中心又是什么?
其實元數據中心僅僅是一個概念上的東西,只要可以存數據,都可以被稱為元數據中心。
Dubbo默認支持三種組件作為元數據中心:
- Redis
- Nacos
- Zookeeper
當你使用Nacos或者Zookeeper作為注冊中心時,Dubbo會默認使用它們作為元數據中心(當然也可以禁用)。并且Nacos使用的是它配置中心的功能。所以在前面的Demo中你就可以在Nacos配置中心模塊中看到下面這條接口和服務名的映射數據。
圖片
Group是mapping,也就是映射的意思。這就是為什么Demo中沒有配置服務提供者也可以調用成功的原因。到這我們來總結一下消費者知道所消費的接口在哪個服務上的兩種方式:
手動配置,有三種不同的方式
- @DubboReference#providedBy屬性配置。
- 通過消費者配置文件配置接口所在的服務名。
- 在消費者配置文件注冊中心的配置中加上接口所在的服務名。
自動探測
服務提供者啟動時將自身所提供的接口和服務名的映射關系存到元數據中心。服務消費者在啟動的時候,會去從元數據中心查到自己所消費的接口屬于哪個服務。自動探測方式需要引入元數據中心,使用Nacos或者Zookeeper作為注冊中心時,Dubbo默認會使用它們作為元數據中心。如果項目中沒有使用元數據中心,那么只能使用第一種手動配置的方式。
2、服務接口詳情數據如何獲???
通過上一節的方式我們可以成功知道消費者所消費的接口在哪個服務上。但是僅僅知道接口在哪個服務上還是無法調用。因為必須得知道接口使用IP、端口、通信協議等。所以消費者此時就會進行第二步,獲取接口詳情數據。Dubbo也提供了兩種獲取方式。第一種,從服務提供者本地緩存中獲取,這種方式也是默認的。對于接口服務提供者來說,它會將接口詳情數據存到本地緩存。所以消費者可以從服務本地緩存中獲取,入下圖所示:
圖片
但是有一個問題,怎么獲取呢?Dubbo做的就很巧妙了。
首先服務提供者在啟動的時候,會去啟動并暴露一個接口是MetadataServiceRPC接口服務。
圖片
之后在注冊服務實例的時候,會將暴露出去的MetadataService這個RPC接口的協議和端口一起存到注冊中心,如下圖所示:
圖片
默認使用的端口就是20880,通信協議是Dubbo協議。由于經過第一步之后,消費者已經知道接口在哪個服務上了。
所以就可以從注冊中心中獲取這個服務對應的服務實例信息。也就能知道MetadataService這個RPC接口所在的服務器IP、端口、通信協議。之后消費者就可以通過這些信息,構建RPC請求,從服務提供者獲取到接口的詳細數據。
整個過程如下圖所示:
圖片
還有一點,之所以說默認是從本地緩存中獲取,是因為在服務實例信息中還存在這么一條信息。
圖片
dubbo.metadata.storage-type=local
消費者在獲取接口詳情數據時,會先判斷dubbo.metadata.storage-type這個屬性值是多少。如果是local,那么就按照前面說的從服務提供者本地緩存中獲取。當然這個配置還可以在服務提供者的配置文件中按照如下方式進行修改。
圖片
如果改成remote,那么應該從哪獲取呢?這就對應第二種情況了,我們接著往下看。
第二種情況也會用到元數據中心。
服務提供者在啟動的時候,會將接口的詳情數據全部存到元數據中心。對于消費者來說,只需要從元數據中心就可以獲取到接口的詳情數據了。
圖片
所以在前面的Demo中你就可以在Nacos配置中心模塊中看到下面這條包含所有接口詳情數據的配置。
圖片
小總結
到這就講完了Dubbo3.x應用級服務注冊的實現原理。
這里我畫一張圖再從整體總結一下前面提到的整個過程。
圖片
首先第一步,需要知道接口在哪個服務上,總共有兩種辦法:
- 手動配置
- 通過服務接口從元數據中心獲取所在的服務名
當僅僅知道接口在哪個服務上還是無法調用,必須知道接口的詳情數據。
接口的詳情數據可以存在兩個地方:
- 服務提供者的本地緩存
- 元數據中心
對于消費者來說,首先得知道應該是從服務提供者的本地緩存還是元數據中心種獲取。所以消費者會先根據第一步獲取到的服務名從服務注冊中心獲取服務實例信息。
因為服務提供者注冊的時候會攜帶dubbo.metadata.storage-type屬性,告訴消費者應該從哪獲取。默認是服務提供者的本地緩存,可通過配置修改。
消費者會根據所配置的屬性值通過對應的方式獲取到接口的詳情數據。之后就可以基于這些接口的詳情信息發送接口級別的RPC調用了。