原文連接:
Apache Dubbo 雲原生服務自省架構設計,來自於微信公衆號:
次靈均閣
隨着微服務架構的推廣和普及,服務之間的耦合度在逐步下降。在演化的過程當中,伴隨着應用組織架構的變化以及基礎設施的衍進,服務和應用之間的邊界變得更爲模糊。Java 做爲一門面向對象的編程語言,Java 接口(interface)做爲服務之間通信的一等公民,配合文檔(JavaDoc)便於開發人員理解和維護。基於相同的編程哲學,Apache Dubbo 做爲傳統的 RPC 服務治理框架,經過接口實現分佈式服務。然而對於微服務治理而言,應用(或「服務」)纔是基礎設施的核心要素。面對雲原生(Cloud Native)技術的興起,傳統的 Dubbo 架構不斷地面臨着新的的挑戰。下面內容將以 Apache Dubbo 2.7.5 爲基礎,介紹全新架構 - Apache Dubbo 服務自省(後文簡稱「服務自省」),瞭解 Dubbo 傳統架構所面臨的現實挑戰,以及服務自省架構的設計和解決之道。html
服務自省是 Dubbo 應用在運行時處理和分析 Dubbo 服務元信息(Metadata)的過程,如當前應用暴露 的Dubbo 服務以及各自的通信協議等。期間會伴隨着事件的廣播和處理,如服務暴露事件。Dubbo 服務自省架構是其傳統架的一種補充,更是將來 Dubbo 架構,它更適合如下使用場景:java
若是 Dubbo 集羣規模超過一千以上,或者集羣擴縮容已沒法自如地執行,如 Zookeeper 管理數萬 Dubbo 服務,服務自省可極大化減輕註冊中心的壓力,尤爲在內存足跡、網絡傳輸以及變動通知上體現。git
若是想要 Dubbo 應用更好地微服務化,或者更接近於雲原生應用,那麼服務自省是一種不錯的選擇,它可以提供已應用爲粒度的服務註冊與發現模型,全面地支持最流行的 Spring Cloud 和 Kubernetes 註冊中心,而且能與 Spring Cloud 或 Spring Boot 應用交互。github
Dubbo 元數據架構是圍繞 Dubbo DevOps 而引入,包括 Dubbo 配置元數據(如:屬性配置、路由規則等)和結構元數據(如:Java 註解、接口和文檔等)。服務自省做爲 Dubbo 元數據的基礎設施,不只支持全部元數據的存儲和平滑升級,並且不會對註冊中心、配置中心和元數據中心產生額外的負擔。spring
Apache Dubbo 是一款面向接口代理的高性能 RPC 框架,提供服務註冊與發現的特性,其基礎架構以下圖所示:apache
(圖 1)編程
綜上所述,Dubbo 註冊與發現的對象是 Dubbo 服務(Java 接口),而其載體爲註冊元信息,即 Dubbo URL,如:dubbo://192.168.1.2:20880/com.foo.BarService?version=1.0.0&group=default
,一般包含必須信息,如服務提供方 IP 和端口、 Java 接口,可選包含版本(version)和分組(group)等。服務 URL 所包含的信息可以惟一界別服務提供方的進程。segmentfault
爲了更好地符合 Java 開發人員的編程習慣,Dubbo 以 Java 服務接口做爲註冊對象,所面臨的現實挑戰主要有:api
Dubbo 註冊中心是中心化的基礎設施,大多數註冊中心的實現爲內存型存儲,好比 Zookeeper、Nacos 或 Consul、Eureka。註冊中心的內存消耗與 Dubbo 服務註冊的數量成正比,任一 Dubbo Provider 容許註冊 N 個 Dubbo 服務接口,當 N 越大,註冊中心的負載越重。根據不徹底統計,Dubbo 核心 Provider 用一般會暴露 20 ~ 50 個服務接口。註冊中心是中心化的基礎設施,其穩定性面臨嚴峻考驗。儘管微服務架構不斷地深化,然而現實狀況是,更多開發者仍舊願意在單一 Provider 上不斷地增長 Dubbo 服務接口,而非更細粒度的 Dubbo Provider 組織。緩存
爲了不單點故障,主流的註冊中心均提供高可用方案。爲解決集羣環境數據同步的難題,內建一致性協議,如 Zookeeper 使用的 Zab 協議,Consul 採用的 Raft 協議。不管哪一種方式,當 Dubbo URL 數量變化頻繁時,網絡和 CPU 壓力也會面臨考驗。若是註冊中心與客戶端之間維持長鏈接狀態的話,如 Zookeeper,註冊中心的網絡負擔會更大。
假設某個 Dubbo Provider 註冊了 N 個 Dubbo 服務接口,當它擴容或縮容 M 個實例(節點)時,N 數量越大,註冊中心至少有 M * N 個 Dubbo URL 註冊或移除。同時,大多數註冊中心實現支持註冊變化通知,如 Zookeeper 節點變化通知。當 Dubbo Consumer 訂閱該 Provider 的 Dubbo 服務接口數爲 X 時,X 數值越大,通知的次數也就越多。實際上,對於來自同一 Provider 的服務接口集合而言,X-1 次通知是重複和無價值的。
若是 Dubbo 註冊實體再也不是服務 URL,而是 Dubbo Provider 節點的話,那麼上述狀況所描述的註冊中心壓力將獲得很大程度的緩解。(負載只有過去的 1/N 甚至更少),然而 Dubbo 如何以應用爲粒度來註冊又是一個新的挑戰。
儘管 Dubbo 也存在應用(Application)的概念,不過傳統的使用場景並不是核心要素,僅在 Dubbo Monitor 或 Dubbo Admin 場景下作辨識之用。隨着微服務架構和雲原生技術的興起,以應用爲粒度的註冊模型已經是大勢所趨,如 Spring Cloud 和 Kubernetes 服務註冊與發現模型。註冊中心所管理的對象一般與業務無關,甚至不具有 RPC 的語義。在術語上,微服務架構中的「服務」(Services)與雲原生中「應用」(Applications)是相同的概念,屬於邏輯名稱,而它們的成員則以服務實例(Service Instances)體現,服務和服務實例的數量關係爲 1:N。
單個服務實例表明一個服務進程,而多個 Dubbo 服務 URL 可隸屬一個 Dubbo Provider 進程,所以,Dubbo URL 與服務實例的數量關係是 N : 1。假設一個 Dubbo Provider 進程僅提供一個 Dubbo 服務(接口)的話,即 N = 1 的狀況,雖然以應用爲粒度的服務註冊與發現可以基於 Dubbo 傳統的 Registry SPI 實現,不過對於現有 Dubbo 應用而言,將存在巨大的應用微服務化工做。
Spring Cloud 是 VMware 公司(前爲 Pivotal)推出的,一套以 Spring 爲技術棧的雲原生(Cloud-Native)解決方案,在 Java 微服務領域具有得天獨厚的優點,擁有超大規模的全球用戶。Spring Cloud 官方支持三種註冊中心實現,包括:Eureka、Zookeeper 和 Consul,Spring Cloud Alibaba 擴展了 Nacos 註冊中心實現。 儘管 Zookeeper、Consul 和 Nacos 也被 Apache Dubbo 官方支持,然而二者的服務註冊與發現的機制不盡相同。
若要 Dubbo 支持 Spring Cloud 服務註冊與發現模型,Dubbo 則需基於 Dubbo Registry SPI 實現,不然底層的變化和兼容性存在風險。
Kubernetes 源自 Google 15 年生產環境的運維經驗,是一個可移植的、可擴展的開源平臺,用於管理容器化的工做負載和服務。Kubernetes 原生服務發現手段主要包括:DNS 和 API Server。DNS 服務發現是一種服務地址的通用方案,不過對於相對複雜 Dubbo 元數據而言,這種服務發現機制或許沒法直接被 Dubbo Registry SPI 適配。相反,API Server 所支持相對更便利,畢竟 Spring Cloud Kubernetes 一樣基於此機制實現,並已在生產環境獲得驗證。換言之,只要 Dubbo 支持 Spring Cloud 服務註冊與發現模型,那麼基於 Kubernetes API Server 的支持也能實現。
所謂兼容 Dubbo 傳統服務註冊與發現模型,包含兩層含義:
Dubbo 從 2.7.0 開始增長了簡化 URL 元數據的特性,被「簡化」的數據存放至元數據中心。因爲 Dubbo 傳統服務註冊與發現模型並未減小 Dubbo 服務 URL 註冊數量。所以,精簡後的 URL 並未明顯地減小注冊中心所承受的壓力。同時,Dubbo URL 元數據精簡模式存在必定的限制,即全部的 Dubbo Provider 節點必須是無狀態的,每一個節點中的 URL 元信息均是一致的,現實中,這個要求很是難以保證,尤爲在同一 Provider 節點存在不一樣的版本或配置的狀況下。綜上所述,Dubbo URL 元數據須要進一步精簡,至少壓力應該避免彙集在註冊中心之上。
架構上,Dubbo 服務自省不只要解決上述挑戰,並且實際場景則更爲複雜,所以,架構細節也將按部就班地展開討論,總體架構可由如下子架構組成:
Dubbo 服務自省首要需求是減輕註冊中心的承載的壓力,同時,以應用爲粒度的服務註冊與發現模型不但可以最大化的減小 Dubbo 服務元信息註冊數量,並且還能支持 Spring Cloud 和 Kubernetes 環境,可謂是一箭雙鵰,架構圖以下所示:
(圖 2)
圖中所示,從 Provider 和 Consumer 向註冊中心註冊的實體再也不是 Dubbo URL,而是服務實例(Service Instance),一個服務實例表明一個 Provider 或 Consumer Dubbo 應用進程。服務實例屬性包括:
注:名稱規則架構上不作約束,不過不一樣註冊中心的規則存在差別
注:若是應用進程暴露多個 Dubbo 協議端口,如 dubbo 和 rest,那麼,服務端口隨機挑選其一,架構上不強制檢驗端口是否可用
上述服務實例模型的支持依賴於註冊中心的實現。換言之,並不是全部註冊中心實現知足服務自省架構的要求。
除了知足服務實例模型的要求以外,註冊中心還得具有如下能力:
業界主流的註冊中心中知足上述要求的有:
總之,Spring Cloud 與 Kubernetes 註冊中心均符合服務自省對註冊中心的要求。不過,在 Dubbo 傳統 RPC 使用場景中,Provider 和 Consumer 關注的是 Dubbo 服務接口,而非 Service 或服務實例。假設須要將現有的 Dubbo 應用遷移至服務自省架構,Provider 和 Consumer 作大量的代碼調整是不現實的。理想的狀況下,兩端實現代碼均無變化,僅修改少許配置,就能達到遷移的效果。那麼,Dubbo 服務接口是如何與 Service 進行映射的呢?
前文曾討論,單個 Dubbo Service 可以發佈多個 Dubbo 服務,因此,Dubbo 服務與 Service 的數量關係是 N 對 1。不過,Dubbo 服務與 Dubbo Service 之間並不存在強綁定關係,換言之,某個 Dubbo 服務也能部署在多個 Dubbo Services 中,所以,Dubbo 服務與 Service 數量關係是 N 對 M(N, M >= 1),以下圖所示:
(圖 3)
上圖中 P1 Service 到 P3 Service 爲 Dubbo Service,com.acme.Interface1 到 com.acme.InterfaceN 則爲 Dubbo 服務接口全稱限定名(QFN)。值得注意的是,Dubbo 服務的 Java 接口(interface)容許不一樣的版本(version)或分組(group),因此僅憑 Java 接口沒法惟一標識某個 Dubbo 服務,還須要增長通信協議(protocol)方可,映射關係更新以下:
(圖 4)
Dubbo 服務 ID 字符表達模式爲: ${protocol}:${interface}:${version}:${group}
, 其中,版本(version)或分組(group)是可選的。當 Dubbo Consumer 訂閱 Dubbo 服務時,構建對應 ID,經過這個 ID 來查詢 Dubbo Provider 的 Service 名稱列表。
因爲 Dubbo 服務與 Service 的映射關係取決於業務場景,架構層面無從預判。所以,這種映射關係只能在 Dubbo 服務暴露時(運行時)才能肯定,不然,Dubbo 服務能被多個 Consumer 應用訂閱時,Consumer 沒法定位 Provider Service 名稱,進而沒法完成服務發現。同時,映射關係的數據一般採用配置的方式來存儲,服務自省提供兩種配置實現,即 「中心化映射配置」 和 「本地化映射配置」。
明顯地,註冊中心來扮演動態映射配置的角色並不適合,否則,Dubbo Service 與映射關係在註冊中心是平級的,不管在理解上,仍是設計上是混亂的。結合 Dubbo 現有基礎設施分析,這個存儲設施可由 Dubbo 配置中心承擔。
其中 Dubbo 2.7.5 動態配置 API(DynamicConfiguration
)支持二級結構,即:group 和 key,其中,group 存儲 Dubbo 服務 ID,而 key 則關聯對應的 Dubbo Service 名稱,對應的 "圖 4」 的數據結構則是:
(圖 5)
如此設計的緣由以下:
利用 DynamicConfiguration#getConfigKeys(String group)
方法,可以輕鬆地經過 Dubbo 服務 ID 獲取其發佈的全部 Dubbo Services,結合服務發現接口獲取服務所部署的 Service 實例集合,最終轉化爲 Dubbo URL
列表。
以 Dubbo 服務 ID dubbo:com.acme.Interface1:default
爲例,它的提供者 Dubbo Services 分別:P1 Service 和 P2 Service。假設配置 Group 爲 "default"(任意名字都可), Key 爲 "dubbo:com.acme.Interface1:default",而內容則是 Dubbo Service 名稱的話。當 P1 Service 和 P2 Service 同時啓動時,不管哪一個 Services 最後完成 Dubbo 服務暴露,那麼,該配置內容必然是二選其一,不管配置中心是否支持原子操做。即便配置中心支持內容追加的特性,因爲兩個 Service 服務實例過程不肯定,配置內容可能會出現重複,如:「P1 Service,P2 Service,P1 Service」。
假設當 P1 Service 存在 5 個服務實例,當 Dubbo 服務 dubbo:com.acme.Interface1:default
(ID)發佈時,配置所關聯的 key 就是當前 Dubbo Service 名稱,即(P1 Service),而內容則是最後發佈該 Dubbo 服務的時間戳(timestamp)。當服務實例越多時,配置中心和網絡傳輸所承受的寫入壓力也就越大。固然架構設計上,服務自省也但願避免重複推送配置,好比在 DynamicConfiguration
API 增長相似於 publishConfigIfAbsent
這樣的方法,不過目前大多數配置中心產品(如:Nacos、Consul)不支持這樣的操做,因此將來服務自省架構會有針對性的提供支持(如:Zookeeper)。
因爲服務自省架構必須依賴註冊中心,同時動態映射配置又依賴配置中心的話,應用的架構複雜度和維護成本均有所提高,不過 Apache Dubbo 所支持的部分註冊中心也可做爲配置中心使用,狀況以下所示:
基礎軟件 | 註冊中心 | 配置中心 |
---|---|---|
Apache Zookeeper | ✅ | ✅ |
HashiCorp Consul | ✅ | ✅ |
Alibaba Nacos | ✅ | ✅ |
Netflix Eureka | ✅ | ❌ |
Kubernetes API Server | ✅ | ❌ |
其中,Zookeeper、Consul 和 Nacos 是目前業界流行的註冊中心,這對於大多數選擇開源產品的應用無疑是一個福音。
若是開發人員認爲配置中心的引入增長了架構的複雜性,那麼,靜態映射配置或許是一種解決方案。
該特性並未在最新 Dubbo 2.7.6 全面發佈,部分特性已在 Dubbo Spring Cloud 中發佈
在 Dubbo 傳統的編程模型中, 常以 Java 註解 @Reference
或 XML 元素 ` 訂閱目標 Dubbo 服務。服務自省架構在此基礎上增長
service` 屬性的映射一個或多個 Dubbo Service 名稱,如:
<reference services="P1 Service,P2 Service" interface="com.acme.Interface1" />
或
@Reference(services="P1 Service,P2 Service") private com.acme.Interface1 interface1;
如此配置後,Dubbo 服務 com.acme.Interface1
將向 p1-service
和 p2-service
訂閱服務。若是開發人員認爲這種方式會侵入到代碼,服務自省還提供外部化配置方式配置映射。
服務自省架構支持外部化配置的方式聲明「Dubbo 服務與 Service 映射」,配置格式爲 Properties
,以圖 4 爲例,內容以下:
dubbo\:com.acme.Interface1\:default = P1 Service,P2 Service thirft\:com.acme.InterfaceX = P1 Service,P3 Service rest\:com.acme.interfaceN = P1 Service
除此以外,Dubbo Spring Cloud 提供應用級別的 Dubbo 服務映射配置,即 dubbo.cloud.subscribed-services
,例如:
dubbo: cloud: subscribed-services: P1 Service,P3 Service
總之,不管是映射配置的方式是中心化仍是本地化,服務 Consumer 依賴這些數據來定位 Dubbo Provider Services,再經過服務發現 API 結合 Service 名稱(列表)獲取服務實例集合,爲合成 Dubbo URL 作準備:
(圖 6)
不過,映射關係並不是是一種強約束,Dubbo Provider 的服務是否可用的檢驗方法是探測目標 Dubbo Service 是否存在,並需確認訂閱的 Dubbo 服務在目標 Services 是否真實暴露,所以,服務自省引入了 Dubbo 元數據服務架構,來完成 Dubbo 服務 URL 的存儲。
Dubbo 元數據服務是一個常規的 Dubbo 服務,爲服務訂閱端提供 Dubbo 元數據的服務目錄,相似於 WebServices 中的 WDSL 或 REST 中的 HATEOAS,幫助 Dubbo Consumer 獲取訂閱的 Dubbo 服務的 URL 列表。元數據服務架構沒法獨立於服務註冊與發現架構而存在,下面經過「總體架構」的討論,瞭解二者之間的關係。
架構上,不管 Dubbo Service 屬於 Provider 仍是 Consumer,甚至是二者的混合,每一個 Dubbo (Service)服務實例有且僅有一個 Dubbo 元數據服務。換言之,Dubbo Service 不存在純粹的 Consumer,即便它不暴露任何業務服務,那麼它也多是 Dubbo 運維平臺(如 Dubbo Admin)的 Provider。不過出於行文的習慣,Consumer 仍舊被定義爲 Dubbo 服務消費者(應用)。因爲每一個 Dubbo Service 均發佈自身的 Dubbo 元數據服務,那麼,架構不會爲不一樣的 Dubbo Service 設計獨立的元數據服務接口(Java)。換言之,全部的 Dubbo Service 元數據服務接口是統一的,命名爲 MetadataService
。
從 Dubbo 服務(URL)註冊與發現的視角, MetadataService
扮演着傳統 Dubbo 註冊中心的角色。綜合服務註冊與發現架構(Dubbo Service 級別),微觀架構以下圖所示:
(圖 7)
**
**
對於 Provider(服務提供者)而言,Dubbo 應用服務暴露與傳統方式無異,而 MetadataService
的暴露時機必須在它們完成後,同時, MetadataService
須要收集這些 Dubbo 服務的 URL(存儲細節將在「元數據服務存儲模式「 小節討論)。假設某個 Provider 的 Dubbo 應用服務暴露數量爲 N,那麼,它全部的 Dubbo 服務暴露數量爲 N + 1。
對於 Consumer(服務消費者)而言,獲 Dubbo 應用服務訂閱 URL 列表後,Dubbo 服務調用的方式與傳統方式是相同的。不過在此以前,Consumer 須要經過 MetadataService
合成訂閱 Dubbo 服務的 URL。該過程之因此稱之爲「合成」,而非「獲取,是由於一次 MetadataService
服務調用僅在其 Provider 中的一臺服務實例上執行,而該 Provider 可能部署了 N 個服務實例。具體「合成」的細節須要結合「宏觀架構」來講明。
元數據服務的宏觀架構依賴於服務註冊與發現架構,以下圖所示:
(圖 8)
圖 8 中 p 和 c 分別表明 Provider 和 Consumer 的執行動做,後面緊跟的數字表示動做的次序,從 0 開始計數。執行動做是串行的,並屬於 Fast-Fail 設計,若是前階段執行失敗,後續動做將不會發生。之因此如此安排是爲了確保 MetadataService
可以暴露和消費。首先從 Provider 執行流程開始說明。
MetadataService
,該步驟徹底由框架自行處理,不管是否 p0 是否暴露 Dubbo 服務MetadataService
的元數據先同步到服務實例(Service Instance)的元數據。隨後,執行服務實例註冊MetadataService
的元數據MetadataService
Dubbo 調用客戶端(代理)MetadataService
Dubbo 調用,獲取該服務實例 Px 所暴露的 Dubbo 應用服務 URL 列表不難看出,上述架構以及流程結合了「服務註冊與發現」與「元數據服務」雙架構,步驟之間會觸發相關 Dubbo 事件,如「服務實例註冊前事件」等。換言之,三種架構綜合體也就是服務自省架構。
至此,關於 Dubbo 服務自省架構設計方面,還存在一些細節亟待說明,好比:
MetadataService
怎樣體現差別呢?MetadataService
做爲一個常規的 Dubbo 服務,它的註冊元信息存放在何處?MetadataService
做爲服務目錄,它管理的 Dubbo 應用服務 URL 是如何存儲的?元數據服務 Metadata,稱之爲「元數據服務的元數據」,主要包括:
MetadataService
MetadataService
所部署的 Dubbo Service 名稱,做爲 MetadataService
分組信息MetadataService
分組,數據使用 serviceNameMetadataService
的版本,版本號一般在接口層面聲明,不一樣的 Dubbo 發行版本 version 可能相同,好比 Dubbo 2.7.5 和 2.7.6 中的 version 均爲 1.0.0。理論上,version 版本越高,支持元信息類型更豐富MetadataService
所暴露協議,爲了確保 Provider 和 Consumer 通信兼容性,默認協議爲:「dubbo」,也能夠支持其餘協議。MetadataService
所在的服務實例主機或 IPMetadataService
暴露後 URL 中的參數信息不可貴出,憑藉以上元數據服務的 Metadata,可將元數據服務的 Dubbo 服務 ID 肯定,輔助 Provider 服務暴露和 Consumer 服務訂閱 MetadataService
。不過對於 Provider,這些元信息都是已知的,而對 Consumer 而言,它們直接能獲取的元信息僅有:
MetadataService
,由於 Provider 和 Consumer 公用 MetadataService
接口不過 Consumer 合成 MetadataService
Dubbo URL 還需獲取 version、host、port、protocol 以及 params:
MetadataService
接口是統一接口,然而 Provider 和 Consumer 可能引入的 Dubbo 版本不一樣,從而它們使用的 MetadataService
version 也會不一樣,因此這個信息須要 Provider 在暴露MetadataService
時,同步到服務實例的 Metadata 中,方便 Consumer 從 Metadata 中獲取經過元數據服務 Metadata 的描述,解釋了不一樣 Dubbo Services 是怎樣體現差別性的,而且說明了 MetadataService
元信息的存儲介質,這也就是服務自省架構爲何強依賴支持 Metadata 的註冊中心的緣由。下個小節將討論 MetadataService
所存儲 Dubbo 應用服務 URL 存放在何處。
Dubbo 2.7.5 在引入 MetadataService
的同時,也爲其設計了兩種存儲方式,適用於不一樣的場景,即「本地存儲模式」和「遠程存儲模式」。其中,本地存儲模式是默認選項。
本地存儲模式又稱之爲內存存儲模式(In-Memory),如 Dubbo 應用服務發現和註冊場景中,暴露和訂閱的 URL 直接存儲在內存中。架構上,本地存儲模式的 MetadataService
至關於去中心化的 Dubbo 應用服務的註冊中心。
遠程存儲模式,與去中心化的本地存儲模式相反,採用 Dubbo 元數據中心來管理 Dubbo 元信息,又稱之爲元中心化存儲模式(Metadata Center)。
爲了減小負載壓力和維護成本,服務自省中的元數據服務推薦使用「本地存儲模式」。
回顧前文「Consumer 執行流程」中的步驟 c3.e,爲了減小 MetadataService
調用次數,服務自省將第一次的調用結果做爲模板,再結合其餘 N-1 服務實例的元信息,合成完整的 N 臺服務實例的 Dubbo 元信息。假設,Dubbo Service 服務實例中部署的 Dubbo 服務數量和內容不一樣,那麼,c3.e 的執行步驟是存在問題的。所以,服務自省引入「Dubbo 服務修訂版本」的機制來解決不對等部署的問題。
儘管「Dubbo 服務修訂版本」機制可以介紹 MetadataService
總體消費次數,然而當新修訂版本的服務實例過少,而且 Consumer 過多時,如新的版本 Provider 應用分批部署,每批的服務實例爲 1 臺,而其 Consumer 服務實例成千上萬。爲了確保這類場景的穩定性,Provider 和 Consumer 的 MetadataService
可選擇「遠程存儲模式」,避免消費熱點的發生。
當業務出現變化時,Dubbo Service 的 Dubbo 服務也會隨之升級。一般,Provider 先行升級,Consumer 隨後跟進。
考慮如下場景,Provider 「P1」 線上已發佈 interface 爲 com.acme.Interface1
,group 爲 group
, version 爲 v1
,即 Dubbo 服務 ID 爲:dubbo:com.acme.Interface1:v1:default
。P1 可能出現升級的狀況有:
因爲 Dubbo 基於 Java 接口來暴露服務,同時 Java 接口一般在 Dubbo 微服務中又是惟一的。若是 interface 的全類名調整的話,那麼,至關於 com.acme.Interface1
作下線處理,Consumer 將沒法消費到該 Dubbo 服務,這種狀況不予考慮。若是是 Provider 新增服務接口的話,那麼 com.acme.Interface1
則並無變化,也無需考慮。因此,有且僅有一種狀況考慮,即「Dubbo interface 方法聲明升級」,包括:
假設 P1 在升級過程當中,新的服務實例部署僅存在調整 group 後的 Dubbo 服務,如 dubbo:com.acme.Interface1:v1:test
,那麼這種升級就是不兼容升級,在新老交替過程當中,Consumer 僅能消費到老版本的 Dubbo 服務。當新版本徹底部署完成後,Consumer 將沒法正常服務調用。若是,新版本中 P1 同時部署了 dubbo:com.acme.Interface1:v1:default
和 dubbo:com.acme.Interface1:v1:test
的話,至關於 group 並沒有變化。同理,version 和 protocol 變化,至關於 Dubbo 服務 ID 變化,這類狀況無需處理。
這是一種比較特殊的升級方法,即 Provider 全部服務實例 Dubbo 服務 ID 相同,然而 Dubbo 服務的參數在不一樣版本服務實例存在差別,假設 Dubbo Service P1 部署 5 臺服務,其中 3 臺服務實例設置 timeout 爲 1000 ms,其他 2 臺 timeout 爲 3000 ms。換言之,P1 擁有兩個版本(狀態)的 MetadataService
。
綜上所述,不管是 Dubbo interface 方法聲明升級,仍是 Dubbo 服務元數據升級,都可認爲是 Dubbo 服務升級的因子,這些因子所計算出來的數值稱之爲「Dubbo 服務修訂版本」,服務自省架構將其命名爲「revision」。架構設設計上,當 Dubbo Service 增長或刪除服務方法、修改方法簽名以及調整 Dubbo 服務元數據,revision 也會隨之變化,revision 數據將存放在其 Dubbo 服務實例的 metadata 中。當 Consumer 訂閱 Provider Dubbo 服務元信息時,MetadataService
遠程調用的次數取決於服務實例列表中出現 revision 的個數,總體執行流程以下圖所示:
(圖 9)
MetadataService
的遠程調用,得到 Dubbo URL 列表,並創建 revision 爲 1 的 URL 列表緩存,用 cache = { 1:urls(r1) } 表示大多數狀況,revision 的數量不會超過 2,換言之,Consumer 發起 MetadataService
的遠程調用不會超過 2次。不管 revision 數量的大小,架構可以保證獲取 Dubbo 元信息的正確性。
固然 MetadataService
並不是僅支持 Dubbo URL 元數據,還有其餘類型的支持。
架構上,元數據服務(MetadataService
)將來將逐步替代 Dubbo 2.7.0 元數據中心,並隨着 Dubbo 版本的更迭,所支持的元數據類型也將有所變化,好比 Dubbo 2.7.5 元數據服務支持的類型包括:
當前 Dubbo Service 暴露或發佈 Dubbo 服務 URL 集合,如:[ dubbo://192.168.1.2:20880/com.acme.Interface1?group=default&version=v1
, thirft://192.168.1.2:20881/com.acme.InterfaceX
, rest://192.168.1.2:20882/com.acme.interfaceN
]
當前 Dubbo Service 全部訂閱的 Dubbo 服務 URL 集合,該元數據主要被 Dubbo 運維平臺來收集。
Dubbo 服務提供方(Provider)在服務暴露的過程當中,將元信息以 JSON 的格式同步到註冊中心,包括服務配置的所有參數,以及服務的方法信息(方法名,入參出參的格式)。在服務自省引入以前,該元數據被 Dubbo 2.7.0 元數據中心 存儲,如:
{ "parameters": { "side": "provider", "methods": "sayHello", "dubbo": "2.0.2", "threads": "100", "interface": "org.apache.dubbo.samples.metadatareport.configcenter.api.AnnotationService", "threadpool": "fixed", "version": "1.1.1", "generic": "false", "revision": "1.1.1", "valid": "true", "application": "metadatareport-configcenter-provider", "default.timeout": "5000", "group": "d-test", "anyhost": "true" }, "canonicalName": "org.apache.dubbo.samples.metadatareport.configcenter.api.AnnotationService", "codeSource": "file:/../dubbo-samples/dubbo-samples-metadata-report/dubbo-samples-metadata-report-configcenter/target/classes/", "methods": [{ "name": "sayHello", "parameterTypes": ["java.lang.String"], "returnType": "java.lang.String" }], "types": [{ "type": "java.lang.String", "properties": { "value": { "type": "char[]" }, "hash": { "type": "int" } } }, { "type": "int" }, { "type": "char" }] }
在架構上,元數據服務(MetadataService
)所支持元數據類型是不限制的,以下圖所示:
(圖 10)
除上文曾討論的三種元數據類型,還包括「Dubbo 服務 REST 元信息」 和 「其餘元信息」。其中,Dubbo 服務 REST 元信息包含 Dubbo 服務 與 REST 映射信息,可用於 Dubbo 服務網關,而其餘元信息可能包括 Dubbo 服務 JavaDoc 元信息,可用於 Dubbo API 文檔。
考慮到 Dubbo Provider 和 Consumer 可能依賴不一樣發行版本的 MetadataService
,所以,Provider 提供的和 Consumer 所須要的元數據類型並不對等,如 Provider 使用 Dubbo 版本爲 2.7.5,該發行版本僅支持「Dubbo 暴露的服務 URL 列表」,「Dubbo 訂閱的服務 URL 列表」和「Dubbo 服務定義」,這三種元數據分別來源於接口的三個方法。當 Consumer 使用了更高的 Dubbo 版本,並須要獲取「Dubbo 服務 REST 元信息」時,天然沒法從 Provider 端獲取。假設 MetadataService
爲其新增一個方法,那麼,當 Consumer 發起調用時,那麼這個調用天然會失敗。即便兩端使用的版本相同,那麼 Provider 仍有可能選擇性支持特定的元數據類型。爲了確保元數據接口的兼容性,MetadataService
應具有元數據類型支持的判斷。如此設計,MetadataService
在元數據類型上支持更具備彈性。
相較於傳統的 Dubbo 架構,服務自省架構的執行流程更爲複雜,執行動做之間的關聯很是緊密,如 Dubbo Service 服務實例註冊前須要完成 Dubbo 服務 revision 的計算,並將其添加至服務實例的 metadata 中。又如當 Dubbo Service 服務實例出現變化時,Consumer 元數據須要從新計算。這些動做被 「事件」(Event
)驅動,驅動者被定義爲「事件分發器」( EventDispatcher
),而動做的處理則由「事件監聽器」(EventListener
)執行,三者均爲 「Dubbo 事件"的核心組件,一樣由 Dubbo 2.7.5 引入。不過,Dubbo 事件是相對獨立的架構,不過被服務自省中的「服務註冊與發現架構」和「元數據服務架構」依賴。
Dubbo 內建事件可概括爲如下類型:
事件類型 | 事件觸發時機 |
---|---|
ServiceConfigExportedEvent |
當 Dubbo 服務暴露完成時 |
ServiceConfigUnexportedEvent |
當 Dubbo 服務下線後 |
ReferenceConfigInitializedEvent |
當 Dubbo 服務引用初始化後 |
ReferenceConfigDestroyedEvent |
當 Dubbo 服務引用銷燬後 |
事件類型 | 事件觸發時機 |
---|---|
DubboShutdownHookRegisteredEvent |
當 Dubbo ShutdownHook 註冊後 |
DubboShutdownHookUnregisteredEvent |
當 Dubbo ShutdownHook 註銷後 |
DubboServiceDestroyedEvent |
當 Dubbo 進程銷燬後 |
事件類型 | 事件觸發時機 |
---|---|
ServiceInstancePreRegisteredEvent |
當 Dubbo 服務實例註冊前 |
ServiceInstanceRegisteredEvent |
當 Dubbo 服務實例註冊後 |
ServiceInstancePreUnregisteredEvent |
當 Dubbo 服務實例註銷前 |
ServiceInstanceUnregisteredEvent |
當 Dubbo 服務實例註銷後 |
ServiceInstancesChangedEvent |
當 某個 Dubbo Service 下的服務實例列表變動時 |
事件類型 | 事件觸發時機 |
---|---|
ServiceDiscoveryInitializingEvent |
當 Dubbo 服務註冊與發現組件初始化中 |
ServiceDiscoveryInitializedEvent |
當 Dubbo 服務註冊與發現組件初始化後 |
ServiceDiscoveryExceptionEvent |
當 Dubbo 服務註冊與發現組件異常發生時 |
ServiceDiscoveryDestroyingEvent |
當 Dubbo 服務註冊與發現組件銷燬中 |
ServiceDiscoveryDestroyedEvent |
當 Dubbo 服務註冊與發現組件銷燬後 |
512
)512
)