做者 | 劉軍(陸龜) Apache Dubbo PMChtml
社區版本 Dubbo 從 2.7.5 版本開始,新引入了一種基於實例(應用)粒度的服務發現機制,這是咱們爲 Dubbo 適配雲原生基礎設施的一步重要探索。版本發佈到如今已有近半年時間,通過這段時間的探索與總結,咱們對這套機制的可行性與穩定性有了更全面、深刻的認識;同時在 Dubbo 3.0 的規劃也在全面進行中,如何讓應用級服務發現成爲將來下一代服務框架 Dubbo 3.0 的基礎服務模型,解決雲原生、規模化微服務集羣擴容與可伸縮性問題,也已經成爲咱們當前工做的重點。apache
既然這套新機制如此重要,那它究竟是怎麼工做的呢?今天咱們就來詳細解讀一下。在最開始的社區版本,咱們給這個機制取了一個神祕的名字 - 服務自省,下文將進一步解釋這個名字的由來,並引用服務自省代指這套應用級服務發現機制。編程
熟悉 Dubbo 開發者應該都知道,一直以來都是面向 RPC 方法去定義服務的,而且這也是 Dubbo 開發友好性、治理功能強的基礎。既然如此,那咱們爲何還要定義個應用粒度的服務發現機制呢?這個機制究竟是怎麼工做的?它與當前機制的區別是什麼?它能給咱們帶來哪些好處那?對適配雲原生、性能提高又有哪些幫助?後端
帶着全部的這些問題,咱們開始本文的講解。api
首先,咱們先來解釋文章開篇提到的問題:架構
所謂「應用/實例粒度」 或者「RPC 服務粒度」強調的是一種地址發現的數據組織格式。app
以 Dubbo 當前的地址發現數據格式爲例,它是「RPC 服務粒度」的,它是以 RPC 服務做爲 key,以實例列表做爲 value 來組織數據的:負載均衡
"RPC Service1": [ {"name":"instance1", "ip":"127.0.0.1", "metadata":{"timeout":1000}}, {"name":"instance2", "ip":"127.0.0.1", "metadata":{"timeout":2000}}, {"name":"instance3", "ip":"127.0.0.1", "metadata":{"timeout":3000}}, ] "RPC Service2": [Instance list of RPC Service2], "RPC ServiceN": [Instance list of RPC ServiceN]
而咱們新引入的「應用粒度的服務發現」,它以應用名(Application)做爲 key,以這個應用部署的一組實例(Instance)列表做爲 value。這帶來兩點不一樣:框架
"application1": [ {"name":"instance1", "ip":"127.0.0.1", "metadata":{}}, {"name":"instance2", "ip":"127.0.0.1", "metadata":{}}, {"name":"instanceN", "ip":"127.0.0.1", "metadata":{}} ]
要進一步理解新模型帶來的變化,咱們看一下應用與 RPC 服務間的關係,顯而易見的,1 個應用內可能會定義 n 個 RPC Service。所以 Dubbo 以前的服務發現粒度更細,在註冊中心產生的數據條目也會更多(與 RPC 服務成正比),同時也存在必定的數據冗餘。less
簡單理解了應用級服務發現的基本機制,接着解釋它爲何會被叫作「服務自省」?
其實這仍是得從它的工做原理提及,上面咱們提到,應用粒度服務發現的數據模型有幾個如下明顯變化:數據中心的數據量少了,RPC 服務相關的數據在註冊中心沒有了,如今只有 application - instance 這兩個層級的數據。爲了保證這部分缺乏的 RPC 服務數據仍然能被 Consumer 端正確的感知,咱們在 Consumer 和 Provider 間創建了一條單獨的通訊通道:Consumer 和 Provider 兩兩之間經過特定端口交換信息,咱們把這種 Provider 本身主動暴露自身信息的行爲認爲是一種內省機制,所以從這個角度出發,咱們把整個機制命名爲:服務自省。
上面講服務自省的大概原理的時候也提到了它給註冊中心帶來的幾點不一樣,這幾點不一樣體如今 Dubbo 框架側(甚至整個微服務體系中),有如下優點:
自動、透明的實例地址發現(負載均衡)是全部微服務框架須要解決的事情,這能讓後端的部署結構對上游微服務透明,上游服務只須要從收到的地址列表中選取一個,發起調用就能夠了。要實現以上目標,涉及兩個關鍵點的自動同步:
對於 RPC 實例間藉助註冊中心的數據同步,REST 定義了一套很是有意思的成熟度模型,感興趣的朋友能夠參考這裏的連接 :
https://www.martinfowler.com/articles/richardsonMaturityModel.html
按照文章中的 4 級成熟度定義,Dubbo 當前基於接口粒度的模型能夠對應到 L4 級別。
接下來,咱們看看 Dubbo、SpringCloud 以及 Kubernetes 分別是怎麼圍繞自動化的實例地址發現這個目標設計的。
Spring Cloud 經過註冊中心只同步了應用與實例地址,消費方能夠基於實例地址與服務提供方創建連接,可是消費方對於如何發起 HTTP 調用(SpringCloud 基於 rest 通訊)一無所知,好比對方有哪些 HTTP endpoint,須要傳入哪些參數等。
RPC 服務這部分信息目前都是經過線下約定或離線的管理系統來協商的。這種架構的優缺點總結以下:
Dubbo 經過註冊中心同時同步了實例地址和 RPC 方法,所以其能實現 RPC 過程的自動同步,面向 RPC 編程、面向 RPC 治理,對後端應用的拆分消費端無感知,其缺點則是地址推送數量變大,和 RPC 方法成正比。
Dubbo 要支持 Kubernetes native service,相比以前自建註冊中心的服務發現體系來講,在工做機制上主要有兩點變化:
Kubernetes Service 做爲一個抽象概念,怎麼映射到 Dubbo 是一個值得討論的點
Service Name - > Application Name,Dubbo 應用和 Kubernetes 服務一一對應,對於微服務運維和建設環節透明,與開發階段解耦。
apiVersion: v1 kind: Service metadata: name: provider-app-name spec: selector: app: provider-app-name ports: - protocol: TCP port: targetPort: 9376
Service Name - > Dubbo RPC Service,Kubernetes 要維護調度的服務與應用內建 RPC 服務綁定,維護的服務數量變多。
--- apiVersion: v1 kind: Service metadata: name: rpc-service-1 spec: selector: app: provider-app-name ports: ## ... --- apiVersion: v1 kind: Service metadata: name: rpc-service-2 spec: selector: app: provider-app-name ports: ## ... --- apiVersion: v1 kind: Service metadata: name: rpc-service-N spec: selector: app: provider-app-name ports: ## ...
結合以上幾種不一樣微服務框架模型的分析,咱們能夠發現,Dubbo 與 SpringCloud、Kubernetes 等不一樣產品在微服務的抽象定義上仍是存在很大不一樣的。SpringCloud 和 Kubernetes 在微服務的模型抽象上仍是比較接近的,二者基本都只關心實例地址的同步,若是咱們去關心其餘的一些服務框架產品,會發現它們絕大多數也是這麼設計的;
即 REST 成熟度模型中的 L3 級別。
對比起來 Dubbo 則相對是比較特殊的存在,更多的是從 RPC 服務的粒度去設計的。
對應 REST 成熟度模型中的 L4 級別。
如咱們上面針對每種模型作了詳細的分析,每種模型都有其優點和不足。而咱們最初決定 Dubbo 要作出改變,往其餘的微服務發現模型上的對齊,是咱們最先在肯定 Dubbo 的雲原生方案時,咱們發現要讓 Dubbo 去支持 Kubernetes Native Service,模型對齊是一個基礎條件;另外一點是來自用戶側對 Dubbo 場景化的一些工程實踐的需求,得益於 Dubbo 對多註冊、多協議能力的支持,使得 Dubbo 聯通不一樣的微服務體系成爲可能,而服務發現模型的不一致成爲其中的一個障礙,這部分的場景描述請參見這裏。
這部分涉及到和註冊中心、配置中心的交互,關於不一樣模型下注冊中心數據的變化,以前原理部分咱們簡單分析過。爲更直觀的對比服務模型變動帶來的推送效率提高,咱們來經過一個示例看一下不一樣模型註冊中心的對比:
圖中左邊是微服務框架的一個典型工做流程,Provider 和 Consumer 經過註冊中心實現自動化的地址通知。其中,Provider 實例的信息如圖中表格所示:
應用 DEMO 包含三個接口 DemoService 1 2 3,當前實例的 ip 地址爲 10.210.134.30。
能夠總結出,基於應用粒度的模型所存儲和推送的數據量是和應用、實例數成正比的,只有當咱們的應用數增多或應用的實例數增加時,地址推送壓力纔會上漲。
而對於基於接口粒度的模型,數據量是和接口數量正相關的,鑑於一個應用一般發佈多個接口的現狀,這個數量級自己比應用粒度是要乘以倍數的;另一個關鍵點在於,接口粒度致使的集羣規模評估的不透明,相對於實i例、應用增加都一般是在運維側的規劃之中,接口的定義更多的是業務側的內部行爲,每每能夠繞過評估給集羣帶來壓力。
以 Consumer 端服務訂閱舉例,根據我對社區部分 Dubbo 中大規模頭部用戶的粗略統計,根據受統計公司的實際場景,一個 Consumer 應用要消費(訂閱)的 Provier 應用數量每每要超過 10 個,而具體到其要消費(訂閱)的的接口數量則一般要達到 30 個,平均狀況下 Consumer 訂閱的 3 個接口來自同一個 Provider 應用,如此計算下來,若是以應用粒度爲地址通知和選址基本單位,則平均地址推送和計算量將降低 60% 還要多。
而在極端狀況下,也就是當 Consumer 端消費的接口更多的來自同一個應用時,這個地址推送與內存消耗的佔用將會進一步獲得下降,甚至能夠超過 80% 以上。
一個典型的幾段場景便是 Dubbo 體系中的網關型應用,有些網關應用消費(訂閱)達 100+ 應用,而消費(訂閱)的服務有 1000+ ,平均有 10 個接口來自同一個應用,若是咱們把地址推送和計算的粒度改成應用,則地址推送量從原來的 n 1000 變爲 n 100,地址數量下降可達近 90%。
上面一節咱們從服務模型及支撐大規模集羣的角度分別給出了 Dubbo 往應用級服務發現靠攏的好處或緣由,但這麼作的同時接口粒度的服務治理能力仍是要繼續保留,這是 Dubbo 框架編程模型易用性、服務治理能力優點的基礎。
如下是我認爲咱們作服務模型遷移仍要堅持的設計原則:
應用級服務發現做爲一種新的服務發現機制,和之前 Dubbo 基於 RPC 服務粒度的服務發如今核心流程上基本上是一致的:即服務提供者往註冊中心註冊地址信息,服務消費者從註冊中心拉取&訂閱地址信息。
這裏主要的不一樣有如下兩點:
如下是每一個 Instance metadata 的示例數據,總的原則是 metadata 只包含當前 instance 節點相關的信息,不涉及 RPC 服務粒度的信息。
整體信息歸納以下:實例地址、實例各類環境標、metadata service 元數據、其餘少許必要屬性。
{ "name": "provider-app-name", "id": "192.168.0.102:20880", "address": "192.168.0.102", "port": 20880, "sslPort": null, "payload": { "id": null, "name": "provider-app-name", "metadata": { "metadataService": "{\"dubbo\":{\"version\":\"1.0.0\",\"dubbo\":\"2.0.2\",\"release\":\"2.7.5\",\"port\":\"20881\"}}", "endpoints": "[{\"port\":20880,\"protocol\":\"dubbo\"}]", "storage-type": "local", "revision": "6785535733750099598", } }, "registrationTimeUTC": 1583461240877, "serviceType": "DYNAMIC", "uriSpec": null }
在註冊中心再也不同步 RPC 服務信息後,服務自省在服務消費端和提供端之間創建了一條內置的 RPC 服務信息協商機制,這也是「服務自省」這個名字的由來。服務端實例會暴露一個預約義的 MetadataService RPC 服務,消費端經過調用 MetadataService 獲取每一個實例 RPC 方法相關的配置信息。
當前 MetadataService 返回的數據格式以下:
[ "dubbo://192.168.0.102:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=demo-provider&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=9585&release=2.7.5&side=provider×tamp=1583469714314", "dubbo://192.168.0.102:20880/org.apache.dubbo.demo.HelloService?anyhost=true&application=demo-provider&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=9585&release=2.7.5&side=provider×tamp=1583469714314", "dubbo://192.168.0.102:20880/org.apache.dubbo.demo.WorldService?anyhost=true&application=demo-provider&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=9585&release=2.7.5&side=provider×tamp=1583469714314" ]
熟悉 Dubbo 基於 RPC 服務粒度的服務發現模型的開發者應該能看出來,服務自省機制機制將之前註冊中心傳遞的 URL 一拆爲二:
- 一部分和實例相關的數據繼續保留在註冊中心,如 ip、port、機器標識等;
- 另外一部分和 RPC 方法相關的數據從註冊中心移除,轉而經過 MetadataService 暴露給消費端。
理想狀況下是能達到數據按照實例、RPC 服務嚴格區分開來,但明顯能夠看到以上實現版本還存在一些數據冗餘,有些也數據還未合理劃分。尤爲是 MetadataService 部分,其返回的數據還只是簡單的 URL 列表組裝,這些 URL實際上是包含了全量的數據。
如下是服務自省的一個完整工做流程圖,詳細描述了服務註冊、服務發現、MetadataService、RPC 調用間的協做流程。
在以上流程中,咱們只考慮了一切順利的狀況,但在更詳細的設計或編碼實現中,咱們還須要嚴格約定一些異常場景下的框架行爲。好比,若是消費者 MetadataService 調用失敗,則在重試知道成功以前,消費者將不能夠接收外部流量。
Client 與 Server 間在收到地址推送後的配置同步是服務自省的關鍵環節,目前針對元數據同步有兩種具體的可選方案,分別是:內建 MetadataService;獨立的元數據中心,經過中細化的元數據集羣協調數據。
注意 consumer 端查詢元數據中心的時機,是等到註冊中心的地址更新通知以後。也就是經過註冊中心下發的數據,咱們能明確的知道什麼時候某個實例的元數據被更新了,此時才須要去查元數據中心。
回顧上文講到的註冊中心關於「應用 - 實例列表」結構的數據組織形式,這個變更目前對開發者並非徹底透明的,業務開發側會感知到查詢/訂閱地址列表的機制的變化。具體來講,相比以往咱們基於 RPC 服務來檢索地址,如今 consumer 須要經過指定 provider 應用名才能實現地址查詢或訂閱。
老的 Consumer 開發與配置示例:
<!-- 框架直接經過 RPC Service 1/2/N 去註冊中心查詢或訂閱地址列表 --> <dubbo:registry address="zookeeper://127.0.0.1:2181"/> <dubbo:reference interface="RPC Service 1" /> <dubbo:reference interface="RPC Service 2" /> <dubbo:reference interface="RPC Service N" />
新的 Consumer 開發與配置示例:
<!-- 框架須要經過額外的 provided-by="provider-app-x" 才能在註冊中心查詢或訂閱到地址列表 --> <dubbo:registry address="zookeeper://127.0.0.1:2181?registry-type=service"/> <dubbo:reference interface="RPC Service 1" provided-by="provider-app-x"/> <dubbo:reference interface="RPC Service 2" provided-by="provider-app-x" /> <dubbo:reference interface="RPC Service N" provided-by="provider-app-y" />
以上指定 provider 應用名的方式是 Spring Cloud 當前的作法,須要 consumer 端的開發者顯示指定其要消費的 provider 應用。
以上問題的根源在於註冊中心不知道任何 RPC 服務相關的信息,所以只能經過應用名來查詢。
爲了使整個開發流程對老的 Dubbo 用戶更透明,同時避免指定 provider 對可擴展性帶來的影響(參見下方說明),咱們設計了一套 RPC 服務到應用名的映射關係,以嘗試在 consumer 自動完成 RPC 服務到 provider 應用名的轉換。
Dubbo 之因此選擇創建一套「接口-應用」的映射關係,主要是考慮到 service - app 映射關係的不肯定性。一個典型的場景便是應用/服務拆分,如上面提到的配置 ,PC Service 2 是定義於 provider-app-x 中的一個服務,將來它隨時可能會被開發者分拆到另一個新的應用如 provider-app-x-1 中,這個拆分要被全部的 PC Service 2 消費方感知到,並對應用進行修改升級,如改成 ,這樣的升級成本不能否認仍是挺高的。
究竟是 Dubbo 框架幫助開發者透明的解決這個問題,仍是交由開發者本身去解決,固然這只是個策略選擇問題,而且 Dubbo 2.7.5+ 版本目前是都提供了的。其實我我的更傾向於交由業務開發者經過組織上的約束來作,這樣也可進一步下降 Dubbo 框架的複雜度,提高運行態的穩定性。
應用級服務發現機制是 Dubbo 面向雲原生走出的重要一步,它幫 Dubbo 打通了與其餘微服務體系之間在地址發現層面的鴻溝,也成爲 Dubbo 適配 Kubernetes Native Service 等基礎設施的基礎。
咱們指望 Dubbo 在新模型基礎上,能繼續保留在編程易用性、服務治理能力等方面強大的優點。可是咱們也應該看到應用粒度的模型一方面帶來了新的複雜性,須要咱們繼續去優化與加強;另外一方面,除了地址存儲與推送以外,應用粒度在幫助 Dubbo 選址層面也有進一步挖掘的潛力。
劉軍,Github 帳號 Chickenlj,Apache Dubbo PMC,項目核心開發,見證了Dubbo從重啓開源到Apache畢業的整個流程。現任職阿里云云原生應用平臺團隊,參與服務框架、微服務相關工做,目前主要在推進 Dubbo 3.0 - Dubbo 雲原生。
爲了更多開發者可以享受到 Serverless 帶來的紅利,這一次,咱們集結了 10+ 位阿里巴巴 Serverless 領域技術專家,打造出最適合開發者入門的 Serverless 公開課,讓你即學即用,輕鬆擁抱雲計算的新範式——Serverless。
點擊便可免費觀看課程:https://developer.aliyun.com/learning/roadmap/serverless
「 阿里巴巴雲原生關注微服務、Serverless、容器、Service Mesh 等技術領域、聚焦雲原生流行技術趨勢、雲原生大規模的落地實踐,作最懂雲原生開發者的公衆號。」