Dubbo 邁出雲原生重要一步:應用級服務發現解析

概述

社區版本 Dubbo 從 2.7.5 版本開始,新引入了一種基於實例(應用)粒度的服務發現機制,這是我們爲 Dubbo 適配雲原生基礎設施的一步重要探索。版本發佈到現在已有近半年時間,經過這段時間的探索與總結,我們對這套機制的可行性與穩定性有了更全面、深入的認識;同時在 Dubbo 3.0 的規劃也在全面進行中,如何讓應用級服務發現成爲未來下一代服務框架 Dubbo 3.0 的基礎服務模型,解決雲原生、規模化微服務集羣擴容與可伸縮性問題,也已經成爲我們當前工作的重點。

既然這套新機制如此重要,那它到底是怎麼工作的那,今天我們就來詳細解讀一下。在最開始的社區版本,我們給這個機制取了一個神祕的名字 - 服務自省,下文將進一步解釋這個名字的由來,並引用服務自省代指這套應用級服務發現機制。

熟悉 Dubbo 開發者應該都知道,一直以來都是面向 RPC 方法去定義服務的,並且這也是 Dubbo 開發友好性、治理功能強的基礎。既然如此,那我們爲什麼還要定義個應用粒度的服務發現機制那?這個機制到底是怎麼工作的?它與當前機制的區別是什麼?它能給我們帶來哪些好處那?對適配雲原生、性能提升又有哪些幫助?

帶着所有的這些問題,我們開始本文的講解。

服務自省是什麼?

首先,我們先來解釋文章開篇提到的問題:

1、應用粒度服務發現是到底是一種怎樣的模型,它與當前的 Dubbo 服務發現模型的區別是什麼?

2、我們爲什麼叫它服務自省?

所謂“應用/實例粒度” 或者“RPC 服務粒度”強調的是一種地址發現的數據組織格式。

以 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。這帶來兩點不同:

1、數據映射關係變了,從 RPC Service -> Instance 變爲 Application -> Instance

2、數據變少了,註冊中心沒有了 RPC Service 及其相關配置信息

"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 服務成正比),同時也存在一定的數據冗餘。

簡單理解了應用級服務發現的基本機制,接着解釋它爲什麼會被叫做“服務自省”?其實這還是得從它的工作原理說起,上面我們提到,應用粒度服務發現的數據模型有幾個以下明顯變化:數據中心的數據量少了,RPC 服務相關的數據在註冊中心沒有了,現在只有 application - instance 這兩個層級的數據。爲了保證這部分缺少的 RPC 服務數據仍然能被 Consumer 端正確的感知,我們在 Consumer 和 Provider 間建立了一條單獨的通信通道:Consumer 和 Provider 兩兩之間通過特定端口交換信息,我們把這種 Provider 自己主動暴露自身信息的行爲認爲是一種內省機制,因此從這個角度出發,我們把整個機制命名爲:服務自省。

爲什麼需要服務自省?

上面講服務自省的大概原理的時候也提到了它給註冊中心帶來的幾點不同,這幾點不同體現在 Dubbo 框架側(甚至整個微服務體系中),有以下優勢:

1、與業界主流微服務模型對齊,比如 SpringCloud、Kubernetes Native Service等。

2、提升性能與可伸縮性。註冊中心數據的重新組織(減少),能最大幅度的減輕註冊中心的存儲、推送壓力,進而減少 Dubbo Consumer 側的地址計算壓力;集羣規模也開始變得可預測、可評估(與 RPC 接口數量無關,只與實例部署規模相關)。

對齊主流微服務模型

自動、透明的實例地址發現(負載均衡)是所有微服務框架需要解決的事情,這能讓後端的部署結構對上游微服務透明,上游服務只需要從收到的地址列表中選取一個,發起調用就可以了。要實現以上目標,涉及兩個關鍵點的自動同步:

1、實例地址,服務消費方需要知道地址以建立鏈接

2、RPC 方法定義,服務消費方需要知道 RPC 服務的具體定義,不論服務類型是 rest 或 rmi 等。

對於 RPC 實例間藉助註冊中心的數據同步,REST 定義了一套非常有意思的成熟度模型,感興趣的朋友可以參考這裏的鏈接 :

https://www.martinfowler.com/articles/richardsonMaturityModel.html

按照文章中的 4 級成熟度定義,Dubbo 當前基於接口粒度的模型可以對應到 L4 級別。

接下來,我們看看 Dubbo、SpringCloud 以及 Kubernetes 分別是怎麼圍繞自動化的實例地址發現這個目標設計的。

Spring Cloud

Spring Cloud 通過註冊中心只同步了應用與實例地址,消費方可以基於實例地址與服務提供方建立鏈接,但是消費方對於如何發起 HTTP 調用(SpringCloud 基於 rest 通信)一無所知,比如對方有哪些 HTTP endpoint,需要傳入哪些參數等。

RPC 服務這部分信息目前都是通過線下約定或離線的管理系統來協商的。這種架構的優缺點總結如下。

優勢: 部署結構清晰、地址推送量小;

缺點: 地址訂閱需要指定應用名, provider 應用變更(拆分)需消費端感知;RPC 調用無法全自動同步。

Dubbo

Dubbo 通過註冊中心同時同步了實例地址和 RPC 方法,因此其能實現 RPC 過程的自動同步,面向 RPC 編程、面向 RPC 治理,對後端應用的拆分消費端無感知,其缺點則是地址推送數量變大,和 RPC 方法成正比。

Dubbo + Kubernetes

Dubbo 要支持 Kubernetes native service,相比之前自建註冊中心的服務發現體系來說,在工作機制上主要有兩點變化:

1、服務註冊由平臺接管,provider 不再需要關心服務註冊。

2、consumer 端服務發現將是 Dubbo 關注的重點,通過對接平臺層的 API-Server、DNS 等,Dubbo client 可以通過一個 Service Name(通常對應到 Application Name)查詢到一組 Endpoints(一組運行 provider 的 pod),通過將 Endpoints 映射到 Dubbo 內部地址列表,以驅動 Dubbo 內置的負載均衡機制工作。

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。

1、對於 Spring Cloud 和 Kubernetes 模型,註冊中心只會存儲一條 DEMO - 10.210.134.30+metadata 的數據;

2、對於老的 Dubbo 模型,註冊中心存儲了三條接口粒度的數據,分別對應三個接口 DemoService 1 2 3,並且很多的址數據都是重複的。

可以總結出,基於應用粒度的模型所存儲和推送的數據量是和應用、實例數成正比的,只有當我們的應用數增多或應用的實例數增長時,地址推送壓力纔會上漲。

而對於基於接口粒度的模型,數據量是和接口數量正相關的,鑑於一個應用通常發佈多個接口的現狀,這個數量級本身比應用粒度是要乘以倍數的;另外一個關鍵點在於,接口粒度導致的集羣規模評估的不透明,相對於實i例、應用增長都通常是在運維側的規劃之中,接口的定義更多的是業務側的內部行爲,往往可以繞過評估給集羣帶來壓力。

以 Consumer 端服務訂閱舉例,根據我對社區部分 Dubbo 中大規模頭部用戶的粗略統計,根據受統計公司的實際場景,一個 Consumer 應用要消費(訂閱)的 Provier 應用數量往往要超過 10 個,而具體到其要消費(訂閱)的的接口數量則通常要達到 30 個,平均情況下 Consumer 訂閱的 3 個接口來自同一個 Provider 應用,如此計算下來,如果以應用粒度爲地址通知和選址基本單位,則平均地址推送和計算量將下降 60% 還要多。

而在極端情況下,也就是當 Consumer 端消費的接口更多的來自同一個應用時,這個地址推送與內存消耗的佔用將會進一步得到降低,甚至可以超過 80% 以上。

一個典型的幾段場景即是 Dubbo 體系中的網關型應用,有些網關應用消費(訂閱)達 100+ 應用,而消費(訂閱)的服務有 1000+ ,平均有 10 個接口來自同一個應用,如果我們把地址推送和計算的粒度改爲應用,則地址推送量從原來的 n 1000 變爲 n 100,地址數量降低可達近 90%。

工作原理

設計原則

上面一節我們從服務模型及支撐大規模集羣的角度分別給出了 Dubbo 往應用級服務發現靠攏的好處或原因,但這麼做的同時接口粒度的服務治理能力還是要繼續保留,這是 Dubbo 框架編程模型易用性、服務治理能力優勢的基礎。

以下是我認爲我們做服務模型遷移仍要堅持的設計原則:

1、新的服務發現模型要實現對原有 Dubbo 消費端開發者的無感知遷移,即 Dubbo 繼續面向 RPC 服務編程、面向 RPC 服務治理,做到對用戶側完全無感知。

2、建立 Consumer 與 Provider 間的自動化 RPC 服務元數據協調機制,解決傳統微服務模型無法同步 RPC 級接口配置的缺點。

基本原理詳解

應用級服務發現作爲一種新的服務發現機制,和以前 Dubbo 基於 RPC 服務粒度的服務發現在覈心流程上基本上是一致的:即服務提供者往註冊中心註冊地址信息,服務消費者從註冊中心拉取&訂閱地址信息。

這裏主要的不同有以下兩點:

1、註冊中心數據以“應用 - 實例列表”格式組織,不再包含 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
}

2、Client – Server 自行協商 RPC 方法信息

在註冊中心不再同步 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&timestamp=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&timestamp=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&timestamp=1583469714314"
]

熟悉 Dubbo 基於 RPC 服務粒度的服務發現模型的開發者應該能看出來,服務自省機制機制將以前註冊中心傳遞的 URL 一拆爲二:

  • 一部分和實例相關的數據繼續保留在註冊中心,如 ip、port、機器標識等。
  • 另一部分和 RPC 方法相關的數據從註冊中心移除,轉而通過 MetadataService 暴露給消費端。
    理想情況下是能達到數據按照實例、RPC 服務嚴格區分開來,但明顯可以看到以上實現版本還存在一些數據冗餘,有些也數據還未合理劃分。尤其是 MetadataService 部分,其返回的數據還只是簡單的 URL 列表組裝,這些 URL其實是包含了全量的數據。

以下是服務自省的一個完整工作流程圖,詳細描述了服務註冊、服務發現、MetadataService、RPC 調用間的協作流程。

  • 服務提供者啓動,首先解析應用定義的“普通服務”並依次註冊爲 RPC 服務,緊接着註冊內建的 MetadataService 服務,最後打開 TCP 監聽端口。
  • 啓動完成後,將實例信息註冊到註冊中心(僅限 ip、port 等實例相關數據),提供者啓動完成。
  • 服務消費者啓動,首先依據其要“消費的 provider 應用名”到註冊中心查詢地址列表,並完成訂閱(以實現後續地址變更自動通知)。
  • 消費端拿到地址列表後,緊接着對 MetadataService 發起調用,返回結果中包含了所有應用定義的“普通服務”及其相關配置信息。
  • 至此,消費者可以接收外部流量,並對提供者發起 Dubbo RPC 調用

在以上流程中,我們只考慮了一切順利的情況,但在更詳細的設計或編碼實現中,我們還需要嚴格約定一些異常場景下的框架行爲。比如,如果消費者 MetadataService 調用失敗,則在重試知道成功之前,消費者將不可以接收外部流量。

服務自省中的關鍵機制

元數據同步機制

Client 與 Server 間在收到地址推送後的配置同步是服務自省的關鍵環節,目前針對元數據同步有兩種具體的可選方案,分別是:

  • 內建 MetadataService。
  • 獨立的元數據中心,通過中細化的元數據集羣協調數據。

內建 MetadataService

MetadataService 通過標準的 Dubbo 協議暴露,根據查詢條件,會將內存中符合條件的“普通服務”配置返回給消費者。這一步發生在消費端選址和調用前。

元數據中心:

複用 2.7 版本中引入的元數據中心,provider 實例啓動後,會嘗試將內部的 RPC 服務組織成元數據的格式到元數據中心,而 consumer 則在每次收到註冊中心推送更新後,主動查詢元數據中心。

注意 consumer 端查詢元數據中心的時機,是等到註冊中心的地址更新通知之後。也就是通過註冊中心下發的數據,我們能明確的知道何時某個實例的元數據被更新了,此時才需要去查元數據中心。

RPC 服務 < - > 應用映射關係

回顧上文講到的註冊中心關於“應用 - 實例列表”結構的數據組織形式,這個變動目前對開發者並不是完全透明的,業務開發側會感知到查詢/訂閱地址列表的機制的變化。具體來說,相比以往我們基於 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 映射關係的不確定性。一個典型的場景即是應用/服務拆分,如上面提到的配置<dubbo:reference interface=“RPC Service 2” provided-by=“provider-app-x” />,PC Service 2 是定義於 provider-app-x 中的一個服務,未來它隨時可能會被開發者分拆到另外一個新的應用如 provider-app-x-1 中,這個拆分要被所有的 PC Service 2 消費方感知到,並對應用進行修改升級,如改爲<dubbo:reference interface=“RPC Service 2” provided-by=“provider-app-x-1” />,這樣的升級成本不可否認還是挺高的。
到底是 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 雲原生。

本文轉載自公衆號阿里巴巴中間件(ID:Aliware_2018)。

原文鏈接

https://mp.weixin.qq.com/s?__biz=MzU4NzU0MDIzOQ==&mid=2247489653&idx=1&sn=445692c491f68aed3f649730d3d9ba96&chksm=fdeb2a15ca9ca30322b91029ce3cb6fe49137aaed717b5f5372d2aef72d0ce54658b68cc33cc&scene=27#wechat_redirect

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章