序言
Dubbo在2011開源以後,一直是國內最受歡迎的RPC框架,以後spring boot和Spring Cloud的面世,助推了微服務的火熱程度。計算機的世界變化很快,自從容器和K8s登上舞臺以後,給原有的RPC領域帶來了很大的挑戰。這個文章主要講述RPC領域遇到的問題,以及RPC怎麼去擁抱K8s懷抱的一些思考。
K8S介紹
kubernetes是一個開源的,用於管理雲平臺中多個主機上的容器化的應用,Kubernetes的目標是讓部署容器化的應用簡單而且高效,Kubernetes提供了應用部署,規劃,更新,維護的一種機制。kubernetes簡稱K8s。
在Kubernetes中,最小的管理元素不是一個個獨立的容器,而是Pod。Pod的生命週期須要注意如下幾點:
應用,容器,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。
推薦書籍: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等等。
備註:上述知識是經過查閱資料(Istio官網),以及和集團service mesh同窗溝通得到。若有問題,歡迎指正。
總結
遇到的問題和挑戰
Spring Cloud和Dubbo的共生
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)。分析下應用維度註冊和接口維度註冊的優缺點。會有一篇獨立的文章來闡述應用維度註冊的方案。
接口維度註冊
-
和K8s等主流平臺的模型對應關係不匹配
-
註冊的數據量很是大,有必定的性能風險
應用維度
-
應用拆分或者合併的時候,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獲取數據
服務註冊和發現
-
註冊機制:將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或者其餘協議的請求。
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列表。
參考
服務治理相關的功能,須要在上述服務治理部分中獨立支持。
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。