Dubbo 在 2011 開源之後,一直是國內最受歡迎的 RPC 框架,之後 Spring Boot 和 Spring Cloud 的面世,助推了微服務的火熱程度。電腦的世界變化很快,自從容器和 K8s 登上舞臺之後,給原有的 RPC 領域帶來了很大的挑戰。這個文章主要講述 RPC 領域遇到的問題... ...
序言
Dubbo 在 2011 開源之後,一直是國內最受歡迎的 RPC 框架,之後 Spring Boot 和 Spring Cloud 的面世,助推了微服務的火熱程度。電腦的世界變化很快,自從容器和 K8s 登上舞臺之後,給原有的 RPC 領域帶來了很大的挑戰。這個文章主要講述 RPC 領域遇到的問題,以及 RPC怎麼去擁抱 K8s 懷抱的一些思考。
K8s介紹
Kubernetes 是一個開源的,用於管理雲平臺中多個主機上的容器化的應用, Kubernetes 的目標是讓部署容器化的應用簡單並且高效, Kubernetes 提供了應用部署,規劃,更新,維護的一種機制。Kubernetes 簡稱 K8s。
在 Kubernetes 中,最小的管理元素不是一個個獨立的容器,而是 Pod 。pod 的生命周期需要註意以下幾點:
- 容器和應用可能隨時被殺死
- Pod Ip 和主機名可能變化 (除非使用 StatefulSet 進行定製)
- 寫到本地的磁碟的文件可能消失,如果想不失效,需要用存儲捲
應用,容器,Pod 的關係
- 應用部署在容器中,一般情況下一個應用只部署在一個容器中
- 一個 Pod 下可以包含一個或多個容器,一般情況下一個 Pod 只建議部署一個容器。下列場景除外:
- side car
- 一個容器的運行以來與本地另外一個容器。如一個容器下應用負責下載數據,另外一個容器下應用向外提供服務
Service
如果一些 Pods 提供了一些功能供其它的 Pod 使用,在 Kubernete 集群中是如何實現讓這些前臺能夠持續的追蹤到這些後臺的?答案是:Service 。
Kubernete Service 是一個定義了一組 Pod 的策略的抽象,這些被服務標記的Pod一般都是通過 label Selector 決定的。Service 抽象了對 Pod 的訪問。
預設的 Service ,通過一個集群 Ip 獲取 A Record 。但是有時需要返回所有滿足條件的 Pod ip 列表,這時候可以直接使用 Headless Services 。
參考:https://kubernetes.io/推薦書籍:kubernetes in action
RPC 介紹和分析
隨著微服務的普及,應用之間的通信有了足夠多的成熟方案。Dubbo 在 2011 年開源之後,被大量的中小型公司採用;在 Spring Boot 推出之後,Spring逐漸煥發出第二春,隨即 Spring Cloud 面世,逐漸占領市場,在中國市場中,和 Dubbo 分庭抗爭;gRPC 是 Google 推出的基於 Http2 的端到端的通信工具,逐漸地在k8s市場上占據統治地位,如 etcd,Istio 等都採用 gRPC 作為通信工具;Service Mesh 從開始概念上就火熱,現在逐漸走向成熟,Istio + Envoy (其他 sidecar )逐漸開始走上舞臺。
應用開發者視角
從功能層面來說,對開發者有感知的功能有:服務實現,服務暴露(註解或配置),服務調用(註解或配置),服務治理等。
從選型角度會關註以下幾點:易用性(開發易用性和開箱即用),性能,功能,擴展性等。
框架開發者視角
關鍵流程:服務暴露,服務註冊,服務發現,服務調用,服務治理。
關鍵知識點:序列化,網路通信,服務路由,負載均衡,服務限流,熔斷,降級等服務治理。
主流技術實現
Dubbo / HSF
Dubbo 提供了面向介面的遠程方法調用。應用開發者定義介面,編寫服務並暴露;
Client 端通過介面進行調用。
Dubbo 註冊服務的維度是介面維度,每個介面會在註冊中心寫入一條數據。
Dubbo 支持條件路由,腳本路由,Tag 路由等。這些路由規則都是強依賴於 IP 地址。
備註:Dubbo 和 HSF 的大部分機制都是相似的,所以下麵都以 Dubbo 作為方案進行討論。
SpringCloud
Spring Cloud 通過 Rest 形式進行網路調用。應用開發者可以自己編寫暴露 Rest 服務,如 springmvc 。
Spring Cloud 里的服務註冊是應用維度( Eureka ),Client 端和 Server 端通過約定的方式進行通信。
Spring Cloud 提供了一套標準 API ,而其中 Netflix 是其中的佼佼者,對這套 API 進行了實現,對大部分開發者來說,可以回直接依賴和使用 Netflix ,所以可以說是 Netflix 提供成了 Spring Cloud 的核心。但是作為商業公司對開源投入往往會多變,如 Eureka 已經體制維護。
gRPC
gRPC 是一個基於 HTTP/2 協議設計的 RPC 框架,它採用了 Protobuf 作為 IDL 。gRPC 作為端到端的通信方案,可以解決現在的多語言問題。
gRPC 本身不提供服務註冊,服務治理的功能。但現在可以看到 gRpc 有趨勢往這方面擴展的野心。
K8s
K8s體系裡暫時沒有公允的通信框架,一般推薦 gRPC 作為 RPC 框架。
K8s 體系下,預設情況下, Pod 的 Ip 是變化的,所以 Pod 和 Pod 之間需要通信的話,有幾種方式:
- Service+DNS:新建一個 Service ,可以通過標簽選擇到一組 pod 列表,這個 service 對應一個不變的集群 Ip ;Client 端通過 DNS 方式或者直接訪問集群 Ip 。這個集群 Ip ,約等於實現了負載均衡 ( iptable 方式)。
- headless service:headless service 和上面的 service 的區別是,它不提供集群 Ip ,通過主機名的形式獲取一組 Ip 列表,Client 端自己決定訪問哪個pod。
Istio + Envoy
Istio 的控制層會向 K8s 的 Api server 請求並監聽 pod 信息,service 信息等信息。這樣 Istio 中就有了完整的 K8s 集群中的 Pod,service 等的完整信息。如果K8s 集群中有信息變更,istio 中也可以得到通知並更新對應的信息。
Envoy 作為 Proxy 一個最常見的實現,以 Envoy 作為例子簡單介紹。Envoy 通過查詢文件或管理伺服器來動態發現資源。對應的發現服務及其相應的 Api 被稱作 xDS 。協議內容包括 LDS,RDS,CDS 等等。
參考資料:Service Mesh 介紹:https://www.infoq.cn/article/pattern-service-mesh
Istio 路由規則:https://istio.io/docs/tasks/traffic-management/request-routing/
備註:上述知識是通過查閱資料( Istio 官網),以及和集團 Service Mesh 同學溝通獲得。如有問題,歡迎指正。
小結
遇到的問題和挑戰
Spring Cloud 和 Dubbo 的共生
基礎理論可以參考:https://yq.aliyun.com/articles/585461
Dubbo 預設是基於 TCP 通信,Spring Cloud 大部分基於 Rest 請求。在阿裡雲實施商業化過程中,發現大量公司需要 Spring Cloud 應用和 Dubbo 進行通信,社區主要依靠 Dubbo 上增加一層網關來解決。
是否有方案進行統一服務註冊發現,以及服務調用呢?
Dubbo 在 K8s 場景下的挑戰
K8s 下 pod 的 IP 是變化的 (預設),Dubbo 的服務治理高度依賴 IP 。
K8s 的服務註冊通過 Pod 定義完成,服務發現其實是尋找 Pod 的過程。Pod 和應用有一定的對應關係,和 Dubbo 里的介面維度的服務註冊發現模型不是很匹配。
Dubbo 在 Service Mesh 場景下的生存空間
Dubbo 需要進行支持裁剪,Dubbo 的大部分功能都可以交由 sidecar ( proxy )來完成。
如果公司已經在部署了 RPC 框架,這時候如果需要實施 Service Mesh ,有什麼好的過渡方案嗎?
問題梳理
服務定義
服務怎麼定義呢?需要從應用開發者角度看待怎麼定義服務。
服務在功能維度對應某一功能,如查詢已買訂單詳情。在 Dubbo 中,對應某個介面下的方法;在 Spring Cloud 和 gRPC 對應一個 http 請求。如果從面向函數編程角度,一個服務就是一個 function 。在 Java 語言中,class 是一切編程的基礎,所以將某些服務按照一定的維度進行聚合,放到某個介面中,就成了一個介面包含了很多的服務。
從 Dubbo 角度來解釋下:Dubbo 是面向介面編程的遠程通信,所以 Dubbo 是面向服務集的編程,你如果想調用某個服務,必須通過介面的方式引入,然後調用介面中的某個服務。Dubbo Ops 中提供的服務查詢功能,其實不是查詢單個服務,而是通過查詢介面(服務集)之後獲得具體的方法(服務)。
而在 Spring Cloud 的世界里,服務提供方會將自己的應用信息( Ip+port )註冊成應用下的一個實例,服務消費方和服務提供方按照約定的形式進行 Rest 請求。每個請求對應的也是一個服務。
和 K8s 里的 service 的區別
K8s 里的 service 其實是對應到一組 pod+port 列表,和 DNS 聯繫緊密;用通俗易懂的方式表達:維護了 pod 集合的關係映射。和上面講的服務是屬於不同場景下的兩個概念。
按照這個方式定義服務,服務治理的粒度其實也是按照服務粒度,可以針對每個服務設置超時時間,設置路由規則等等。但是服務註冊的粒度和服務有什麼關係呢?
服務註冊粒度
一個應用下包含了很多介面,一個介面下包含了很多服務( Dubbo );或者一個應用包含了很多的服務( Spring Cloud )。分析下應用維度註冊和介面維度註冊的優缺點。會有一篇獨立的文章來闡述應用維度註冊的方案。
介面維度註冊
優點:
- 服務查詢按照介面維度查詢非常方便,實現難度低
- 應用拆分或者合併的時候, Client 端(消費者)無需關心,做到了讓用戶無感
缺點:
- 和 K8s 等主流平臺的模型對應關係不匹配
- 註冊的數據量非常大,有一定的性能風險
應用維度
優點:
- 和 K8s,Spring Cloud 等模型對應關係一致
- 性能上可以得到很大緩解
缺點:
- 應用拆分或者合併的時候,Client 端需要感知 (如果想做到不感知,需要框架開發者維護一份介面和應用映射關係的存儲)
- 如果想對用戶保持 Dubbo 原有的介面維度的查詢,需要較多的工作量來保證。
- 對用戶透明度有所減少,需要在 OPS 上提供其他一些工具。如供應用開發者可以查看具體某個 Ip 是否提供了某個服務等等。
Dubbo 和 Spring Cloud
目標:Dubbo 和 Spring Cloud 的服務發現進行統一;Dubbo 和 Spring Cloud 可以互相調用。
服務發現統一
Dubbo 改造成應用維度的服務註冊。(具體不展開,後面文章說明)
打通調用
Dubbo 實現中,支持將以 Rest 協議進行暴露,並且讓 Spring Cloud 識別。@桃谷
Dubbo + K8s
在 K8s 已經闡述過,下麵的內容也是假設一個應用部署在一個容器里,一個容器部署在一個 pod 里。
接下來方案的討論,互相之間其實是有關聯的,如服務治理可能會影響到服務註冊發現,服務查詢也不能依賴於服務註冊的內容。整個設計的過程是不斷優化的過程。下麵所說的內容,以 Dubbo 來舉例說明。
服務治理
Dubbo 原有體系裡的服務治理是強依賴於 IP ,當配置了一套服務治理規則的時候,最後都是基於一個或多個 Ip 地址。
到 K8s 體系下之後,要考慮的是 Pod 的 Ip 不是固定的。所以當前的路由規則不能滿足條件,而且會產生很多規則垃圾數據。K8s 體系下,通過 service 查找 Pod ,是基於 label selector ;通過 deployment 管理 Pod ,其實也是基於 Pod label selector 。所以 pod label selector 是在 k8s 習題中比較通用的解決方案。
以路由規則為例,需要支持一種新的路由規則:label 路由。通過一定條件匹配之後,將結果定位到以 label selector 查詢到的 Pod 列表裡,而非原來的 ip 列表。
要支持 label 路由,client 端需要獲取到 client 端自己的 Pod label 信息,還需要獲取到 server pod 列表中每個 Pod 的 label 信息。
應用獲取當前Pod的信息方式
- Pod 定義環境變數,應用獲取Dubbo 提供對環境變數讀取的支持,Pod 中需要按照 Dubbo 定義的環境變數設置具體的 pod 信息。
- 通過 Downward Api 傳遞 Pod 信息Dubbo 需要提供對 Downward 的目錄讀取,Pod 中需要定製 downward 的對應配置。
- 通過 Api server 獲取數據最強大的方式,但是應用需要強依賴於 Api server 。
應用獲取其他 Pod 的信息方式
- 通過調用其他 Pod 的服務獲取依賴於應用能獲取自身的 Pod 信息,同時將自身的 Pod 信息暴露成服務( rest 或 dubbo 協議)client 端通過調用對用的 Pod 獲取到對應 Pod 的完整信息。
- 通過 Api server 獲取數據很強大,但增加了對 Api server 的依賴。
服務註冊和發現
K8s 體系下,RPC 服務發現有幾種方式:
- 註冊機制:將 Ip 寫入註冊中心,用心跳保持連接;當心跳停止,從註冊中心刪除。
- 利用 Service+DNS :新建一個 Service ,可以通過標簽選擇到一組 pod 列表,這個 service 對應一個不變的集群 ip ;Client 端通過 DNS 方式或者直接訪問集群 Ip 。這個集群 Ip ,約等於實現了負載均衡 ( iptable 方式)。
- 利用 headless service(DNS) :headless service 和上面的 service 的區別是,它不提供集群 Ip ,通過主機名的形式獲取一組 Ip 列表,Client 端自己決定訪問哪個 pod 。
- api server :Client 端直接請求 api server ,獲取到 pod 的列表, Client 自己決定訪問 pod 的邏輯。同時獲取的時候增加 watch ,api server 會將 pod 的變化信息同步 Client 。
通過拿到 Server 端的 Ip 或者 host ,Client 端就可以發起 http 或者其他協議的請求。
下麵介紹符合 Dubbo 的可行方案:
1. Dubbo + Zookeeper pod cluster (HSF+CS cluster)
這是最簡單的方式,Dubbo 本身不需要做任何改造。
帶來的問題是增加了 Zookeeper 的維護,同時這個方案很不雲原生,和 K8s 的體系沒有任何關係。
腦暴
上面方案是將 ZooKeeper 作為註冊中心,那麼是否可以將 K8s 里 service 作為註冊中心呢?Dubbo 里每個介面去建立一個 service ,每個應用實例啟動過程中去更新 Endpoint 信息,建立 Service-> Endpoint-> ip 列表的關係。
這種方案中 K8s service 的定義被改造了,而且定義了過多的 service ,service 的維護管理是個難題。
基於 K8s 的場景
在傳統的 RPC 領域,服務分成服務註冊和服務發現。在k8s領域pod和應用是一對一的關係,K8s 本身就提供了pod 查找的能力,所以一定程度上服務註冊其實可以不存在,而只需要服務發現。但是這個其實需要一個前提:
Dubbo需要將服務註冊發現的粒度改造成應用維度。在運維層面,將app=xxx (應用名)寫入到pod的label中。
2. Dubbo + K8s DNS
如果 K8s service 提供了cluster ip ,那麼 Dubbo 只負責調用該集群 Ip ,路由和負載均衡的邏輯則交給了 K8s 的 proxy 來完成。此方案削減了 Dubbo 的核心能力。
接下來討論 headless service 提供的能力。
通過請求 <service>.<ns>.svc.<zone>. IN A 的方式發起請求獲取 Ip 列表,但是需要輪詢方式不斷獲取更新的 Ip 列表。
參考:https://github.com/kubernetes/dns/blob/master/docs/specification.md#24---records-for-a-headless-service
服務治理相關的功能,需要在上述服務治理部分中獨立支持。
3. Dubbo + Api Server
Pod 的容器中部署的 Dubbo 應用,服務註冊流程可以直接刪除,服務發現功能通過和 Api Server 進行交互,獲取 Pod 和 service 信息,同時 watch pod 和service 的變更。通過這種方式之後,服務治理相關的信息也可以通過 Api Server 直接獲取。
4. Dubbo + Istio + Envoy
Dubbo 可以直接使用指定 ip+埠 的方式調用同一個 pod 下 Envoy (也可能是同一個node的Envoy)。Dubbo 將路由規則,負載均衡,熔斷等功能交給Istio和 Envoy 。Envoy 需要支持 Dubbo 協議的轉發。
所以 Dubbo 需要完成兩個事情:本地 IP 直連(現有功能), 多餘功能裁剪(暫未實現)。
5. Dubbo + Istio
Dubbo 應用不再依賴 Envoy 作為 sidecar ,而是直接和 Istio 進行交互,把 Istio 作為註冊中心,作為服務治理的配置中心。
Dubbo 需要提供類似的 xDS 協議,在pilot將service的instance轉換成dubbo的協議格式。
Dubbo 還需要去適配 istio 的一些功能,如健康檢查,安全相關的邏輯。具體實現可以參考 Envoy 的實現。
6. Dubbo和Istio在k8s體系下共存
這個可選擇的方案較多,我提供兩種思路,供大家思考:
所有的服務註冊通過k8s的機制完成,所有的服務發現通過 Headless service 完成。sidecar 在創建過程中,需要對原有的 K8s service 進行 update 。
Nacos 作為 Dubbo 的註冊中心,並且需要將 K8s 中的數據進行部分中轉。Dubbo 應用,將服務註冊以應用維度註冊到 Nacos ,Istio Pilot 需要識別 Nacos 數據;Istio 的運行機制基本不變,需要將 K8s service instance 的數據寫入到 nacos ,供 Dubbo 調用。
7. 雲上和雲下環境共存 & 雲上多集群環境
Istio 提供了跨集群和雲上雲下的解決方案, kubeFed 作為 K8s 的跨集群解決方案也能起到一定作用。
這個課題的複雜度更加高,心中有了一些答案,期望大家通過上文也有一定的思考。
服務查詢
拋出三種方式,供大家思考。
Dubbo 原有方式
Dubbo 原有的服務查詢是針對介面的查詢,每個介面會有版本號和組別。介面名+版本號+組別確定唯一的服務集合,這個服務集下有對應的服務提供者和服務消費者(介面級依賴),服務提供者是一組 ip+port 列表,服務消費者也是一組 ip+port 列表。
當做了改造成應用級別的服務註冊或者直接使用 K8s 自帶的 Pod 發現機制的話,需要做一些改造,這部分改造,和前面提到的一樣,放到其他文章里單獨說明。
只支持應用查詢
和 Spring Cloud 類似,支持應用維度的查詢。查詢到具體應用之後,應用詳情下包含了 ip+port 列表,每個 ip+port 其實就是一個應用的實例。點擊開每個應用實例,可以查看每個應用的詳細信息,詳細信息包含了該實例提供了哪些服務。
介面+應用查詢均衡
在原來只支持應用查詢的基礎上,增加一步:支持查詢某個介面對應的應用列表,而大部分介面只屬於一個應用。
再點擊應用列表下具體的應用之後,會跳到應用詳情。
總結
上述討論的是開源的方案,所以相對歷史包袱比較少。對一些大公司想從原有的 RPC 方案切換到雲原生的支持,需要考慮更多相容性和性能,需要付出更大的代價。
雲原生的趨勢已經勢不可擋,在 RPC 領域究竟哪種方案最終能夠勝出,現在還言之過早。我相信 Service Mesh 和傳統的 RPC (Dubbo/ gRPC) 都會有自己的一席之地,一切讓時間給我們答案吧。
作者簡介:曹勝利,Apache Dubbo PMC,關註 RPC 領域。在阿裡內部負責 Dubbo 開源和 ClassLoader 隔離器 Pandora Boot 。
原文鏈接:https://mp.weixin.qq.com/s/3e2jgHUKFQXpu-pTltRcZw