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

阿里巴巴雲原生發表於2020-06-08


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

作者 | 劉軍(陸龜)  Apache Dubbo PMC


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

概述


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

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

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

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


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

服務自省是什麼?


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

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

  2. 我們為什麼叫它服務自省?


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


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


以 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 邁出雲原生重要一步 - 應用級服務發現解析

為什麼需要服務自省?


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


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

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


1. 對齊主流微服務模型

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


  • 例項地址,服務消費方需要知道地址以建立連結;

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


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


對於 RPC 例項間藉助註冊中心的資料同步,REST 定義了一套非常有意思的成熟度模型,感興趣的朋友可以參考這裡的連結 :

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

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


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


2. Spring Cloud


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


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


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

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


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


3. Dubbo

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


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


4. Dubbo + Kubernetes

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


  • 服務註冊由平臺接管,provider 不再需要關心服務註冊;

  • 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 邁出雲原生重要一步 - 應用級服務發現解析


結合以上幾種不同微服務框架模型的分析,我們可以發現,Dubbo 與 SpringCloud、Kubernetes 等不同產品在微服務的抽象定義上還是存在很大不同的。SpringCloud 和 Kubernetes 在微服務的模型抽象上還是比較接近的,兩者基本都只關心例項地址的同步,如果我們去關心其他的一些服務框架產品,會發現它們絕大多數也是這麼設計的;


即 REST 成熟度模型中的 L3 級別。


對比起來 Dubbo 則相對是比較特殊的存在,更多的是從 RPC 服務的粒度去設計的。


對應 REST 成熟度模型中的 L4 級別。


如我們上面針對每種模型做了詳細的分析,每種模型都有其優勢和不足。而我們最初決定 Dubbo 要做出改變,往其他的微服務發現模型上的對齊,是我們最早在確定 Dubbo 的雲原生方案時,我們發現要讓 Dubbo 去支援 Kubernetes Native Service,模型對齊是一個基礎條件;另一點是來自使用者側對 Dubbo 場景化的一些工程實踐的需求,得益於 Dubbo 對多註冊、多協議能力的支援,使得 Dubbo 聯通不同的微服務體系成為可能,而服務發現模型的不一致成為其中的一個障礙,這部分的場景描述請參見這裡


5. 更大規模的微服務叢集 - 解決效能瓶頸


這部分涉及到和註冊中心、配置中心的互動,關於不同模型下注冊中心資料的變化,之前原理部分我們簡單分析過。為更直觀的對比服務模型變更帶來的推送效率提升,我們來通過一個示例看一下不同模型註冊中心的對比:


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


圖中左邊是微服務框架的一個典型工作流程,Provider 和  Consumer 通過註冊中心實現自動化的地址通知。其中,Provider 例項的資訊如圖中表格所示:


應用 DEMO 包含三個介面 DemoService 1 2 3,當前例項的 ip 地址為 10.210.134.30。


  • 對於 Spring Cloud 和 Kubernetes 模型,註冊中心只會儲存一條 DEMO - 10.210.134.30+metadata 的資料;

  • 對於老的 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 邁出雲原生重要一步 - 應用級服務發現解析

工作原理


1. 設計原則


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


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


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

  • 建立 Consumer 與 Provider 間的自動化 RPC 服務後設資料協調機制,解決傳統微服務模型無法同步 RPC 級介面配置的缺點。


2. 基本原理詳解


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


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


  • 註冊中心資料以“應用 - 例項列表”格式組織,不再包含 RPC 服務資訊;


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


以下是每個 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
}


  • Client – Server 自行協商 RPC 方法資訊。


在註冊中心不再同步 RPC 服務資訊後,服務自省在服務消費端和提供端之間建立了一條內建的 RPC 服務資訊協商機制,這也是“服務自省”這個名字的由來。服務端例項會暴露一個預定義的 MetadataService RPC 服務,消費端通過呼叫 MetadataService 獲取每個例項 RPC 方法相關的配置資訊。


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


當前 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 呼叫間的協作流程。


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


  1. 服務提供者啟動,首先解析應用定義的“普通服務”並依次註冊為 RPC 服務,緊接著註冊內建的 MetadataService 服務,最後開啟 TCP 監聽埠;

  2. 啟動完成後,將例項資訊註冊到註冊中心(僅限 ip、port 等例項相關資料),提供者啟動完成;

  3. 服務消費者啟動,首先依據其要“消費的 provider 應用名”到註冊中心查詢地址列表,並完成訂閱(以實現後續地址變更自動通知);

  4. 消費端拿到地址列表後,緊接著對 MetadataService 發起呼叫,返回結果中包含了所有應用定義的“普通服務”及其相關配置資訊;

  5. 至此,消費者可以接收外部流量,並對提供者發起 Dubbo RPC 呼叫。


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


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

服務自省中的關鍵機制


1. 後設資料同步機制


Client 與 Server 間在收到地址推送後的配置同步是服務自省的關鍵環節,目前針對後設資料同步有兩種具體的可選方案,分別是:內建 MetadataService;獨立的後設資料中心,通過中細化的後設資料叢集協調資料。


  • 內建 MetadataServiceMetadataService 通過標準的 Dubbo 協議暴露,根據查詢條件,會將記憶體中符合條件的“普通服務”配置返回給消費者。這一步發生在消費端選址和呼叫前;

 

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


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


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


2. 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 邁出雲原生重要一步 - 應用級服務發現解析


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 打通了與其他微服務體系之間在地址發現層面的鴻溝,也成為 Dubbo 適配 Kubernetes Native Service 等基礎設施的基礎。


我們期望 Dubbo 在新模型基礎上,能繼續保留在程式設計易用性、服務治理能力等方面強大的優勢。但是我們也應該看到應用粒度的模型一方面帶來了新的複雜性,需要我們繼續去優化與增強;另一方面,除了地址儲存與推送之外,應用粒度在幫助 Dubbo 選址層面也有進一步挖掘的潛力。


作者簡介


劉軍,Github 賬號 Chickenlj,Apache Dubbo PMC,專案核心開發,見證了Dubbo從重啟開源到Apache畢業的整個流程。現任職阿里云云原生應用平臺團隊,參與服務框架、微服務相關工作,目前主要在推動 Dubbo 3.0 - Dubbo 雲原生。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69975341/viewspace-2696797/,如需轉載,請註明出處,否則將追究法律責任。

相關文章