1. 程式人生 > >朱曄的互聯網架構實踐心得S2E4:小議微服務的各種玩法

朱曄的互聯網架構實踐心得S2E4:小議微服務的各種玩法

數據存儲 情況下 disco server 新年快樂 簡單的 方便 和數 rac

十幾年前就有一些公司開始踐行服務拆分以及SOA,六年前有了微服務的概念,於是大家開始思考SOA和微服務的關系和區別。最近三年Spring Cloud的大火把微服務的實踐推到了高潮,而近兩年K8S在容器編排的地位確定之後大家又開始實踐起以K8S為核心的雲原生思想和微服務的結合如何去落地,2018年又多出一個ServiceMesh服務網格的概念,大家又在思考如何引入落地ServiceMesh,ServiceMesh和K8S以及Spring Cloud的關系如何等等。

確實有點亂了,這一波又一波的熱潮,幾乎每兩年都會來一波有關微服務架構理念和平臺,許多公司還沒完成微服務的改造就又提出了服務+容器道路,又有一些公司打算從微服務直接升級成ServiceMesh。本文嘗試總結一下我見過的或實踐過的一些微服務落地方式,並且提出一些自己的觀點,希望拋磚引玉,大家可以暢談一下自己公司的微服務落地方式。

作為一個開發者,有一個學習的氛圍跟一個交流圈子特別重要這是一個我的QQ群架構華山論劍:836442475,不管你是小白還是大牛歡迎入駐 ,分享BAT,阿裏面試題、面試經驗,討論技術, 大家一起交流學習成長!

1、微服務v0.1——古典玩法

技術分享圖片 (圖中灰色部分代表元數據存儲區域,也就是Service和Endpoint關系所保存的地方,之後所有的圖都是這樣)

其實在2006年在使用.NET Remoting做服務拆分的時候(其實當時我們沒有意識到這叫服務拆分,這是打算把一些邏輯使用獨立的進程來承載,以Windows服務形式安裝在不同服務器上分散壓力),我們使用了F5來做服務的負載均衡。沒有所謂的服務發現,針對每一個服務,我們直接在程序配置文件中寫死F5的IP地址和端口,使用Excel來記錄所有服務在F5的端口地址以及服務實際部署的IP:端口,然後在F5進行配置。F5在這裏做了負載均衡、簡單的路由策略(相同的客戶端總是優先路由到相同的後端)以及簡單的白名單策略等等。

2、微服務v0.2——改進版古典玩法

技術分享圖片 之後嘗試過這種改進版的古典玩法。相比v0.1的區別是,不再使用硬件F5了,而是使用幾組軟件反向代理服務器,比如Nginx來做服務負載均衡(如果是TCP的負載均衡的話可以選擇HaProxy),Nginx的配置會比F5更方便而且還不花錢。由於生產環境Nginx可能是多組,客戶端不在配置文件中寫死Nginx地址而是把地址放到了配置中心去,而Nginx的配置由源碼倉庫統一管理,運維通過文件同步方式或其它方式從源碼倉庫拉取配置文件下發到不同的Nginx集群做後端服務的配置(Nginx的配置也不一定需要是一個大文件放所有的配置,可以每一組服務做一個配置文件更清晰)。

雖然我的標題說這是古典玩法,但是可以說很多公司如果沒有上RPC,沒有上Spring Cloud,也沒有上K8S的話很可能就是這樣的玩法。無論是v0.2還是v0.1,本質上服務是固定在虛擬機或實體機部署的,如果需要擴容,需要遷移,那麽肯定需要修改反向代理或負載均衡器的配置。少數情況下,如果調整了反向代理或負載均衡器的IP地址,那麽還可能會需要修改客戶端的配置。

3、微服務v0.5——SOA ESB玩法

技術分享圖片 SOA的一個特點是使用了服務總線,服務總線承擔了服務的發現、路由、協議轉換、安全控制、限流等等。2012年我參與了一個大型MMO遊戲《激戰2》項目的技術整合工作,這個遊戲整個服務端就是這種架構。它有一個叫做Portal的服務總線,所有遊戲的十幾個子服務都會把自己註冊到服務總線,不管是什麽服務需要調用什麽接口,都是在調用服務總線,由服務總線來進行服務的尋址路由和協議轉換,服務總線也會做服務的精細化限流,每一個用戶都有自己的服務請求隊列。這種架構的好處是簡單,服務總線承擔了所有工作,但是服務總線的壓力很大,承擔了所有的服務轉發工作。同時需要考慮服務總線本身如何進行擴容,如果服務總線是有狀態的,顯然要進行擴容不是這麽簡單。對於遊戲服務器來說,擴容可能不是一個強需求,因為遊戲服務天然會按照大區進行分流,一個大區的最大人數上限是固定的。

貌似互聯網公司這樣玩的不多,傳統企業或是遊戲服務端是比較適合服務總線這種架構的,如果服務和服務之間的協議不統一的話,要在客戶端做協議轉換的工作比較痛苦,如果可以由統一的中間層接入所有協議統一進行轉換的話,客戶端會比較輕量,但是這種架構的很大問題在於服務總線的擴容和可靠性。

4、微服務v1.0——傳統服務框架玩法

技術分享圖片 上圖是大多數RPC框架的架構圖。大多數早期的微服務實踐都是RPC的方式,最近幾年Spring Cloud盛行後其實Spring Cloud的玩法也差不多,只是Spring Cloud推崇的是JSON over HTTP的RESTful接口,而大多數RPC框架是二進制序列化over TCP的玩法(也有JSON over HTTP的RPC)。

其實RPC框架我個人喜歡JSON over HTTP,雖然我們知道HTTP和JSON序列化性能肯定不如一些精簡的二進制序列化+TCP,但是優點是良好的可讀性、測試方便、客戶端開發方便,而且我不認為15000的QPS和20000的QPS對於一般應用有什麽區別。

總的來說,我們會有一個集群化的分布式配置中心來充當服務註冊的存儲,比如ZK、Consul、Eureka或etcd。我們的服務框架會有客戶端和服務端部分,客戶端部分會提供服務的發現、軟負載、路由、安全、策略控制等功能(可能也會通過插件形式包含Metrics、Logging、Tracing、Resilience等功能),服務端部分對於RPC框架會做服務的調用也會輔助做一些安全、策略控制,對於RESTful的話就服務端一般除了監控沒有額外的功能。

比如使用Spring Cloud來玩,那麽:

  • Service Discovery:Eureka、Open Feign
  • Load Balancing:Ribbon、Spring Cloud LoadBalancer
  • Metrics:Micrometer、Spring Boot Actuator
  • Resilience:Hystrix、Resilience4j
  • Tracing:Sleuth、Zipkin

在之前《朱曄和你聊Spring系列S1E8:湊活著用的Spring Cloud(含一個實際業務貫穿所有組件的完整例子)》一文中,我有一個完整的例子介紹過Spring Cloud的這套玩法,可以說的確Spring Cloud給了我們構建一套微服務體系最基本的東西,我們只需要進行一些簡單的擴展和補充,比如灰度功能,比如更好的配置服務,就完全可以用於生產。 這種模式和之前0.x的很大區別是,服務的註冊有一個獨立的組件,註冊中心完成,通過配合客戶端類庫的服務發現,至少服務的擴容很輕松,擴容後也不需要手動維護負載均衡器的配置,相當於服務端從死到活的一個重大轉變。而且在1.0的時代,我們更多看到了服務治理的部分,開始意識到成百上千的服務,如果沒有Metrics、Logging、Tracing、Resilience等功能來輔助的話,微服務就是一個災難。

Spring Cloud已經出了G版了,表示Netflix那套已經進入了維護模式,許多程序員表示表示扶我起來還能學。我認為Spring Cloud這個方向其實是挺對的,先有開源的東西來填補空白,慢慢再用自己的東西來替換,但是開發比較苦,特別是一些公司基於Spring Cloud辛苦二次開發的框架圍繞了Netflix那套東西來做的會比較痛苦。總的來說,雖然Spring Cloud給人的感覺很亂,變化很大,大到E到G版的升級不亞於在換框架,而且組件質量層次不齊,但是它確實是一無所有的創業公司能夠起步微服務的不多的選擇之一。如果沒有現成的框架(不是說RPC框架,RPC框架雖是微服務功能的80%重點,但卻是代碼量20%的部分,工作量最大的是治理和整合那套),基於Spring Cloud起步微服務,至少你可以當天起步,1個月完成適合自己公司的二次開發改造。

5、微服務v2.0——容器+K8S容器調度玩法

技術分享圖片 K8S或者說容器調度平臺的引入是比較革命性的,容器使得我們的微服務對環境的依賴可以打包整合進行隨意分發,這是微服務節點可以任意調度的基礎,調度平臺通過服務的分類和抽象,使得微服務本身的部署和維護實現自動化,以及實現更上一層樓的自動伸縮。在1.x時代,服務可以進行擴縮容,但是一切都需要人工介入,在2.x時代,服務本身在哪裏存在甚至有多少實例存在並不重要,重要的只是我們有多少資源,希望服務的SLA是怎麽樣的,其余留給調度平臺來調度。

如果說1.0時代大家糾結過Dubbo還是Spring Cloud,2.0時代我相信也有一些公司上過Mesos的“賊船”,我們不是先知很難預測什麽框架什麽技術會在最後存活下來,但是這卻是也給技術帶來了不少痛苦,相信還是有不少公司在幹Mesos轉K8S的事情。

如果引入了K8S,那麽服務發現可以由K8S來做,不一定需要Eureka。我們可以為Pod創建Service,通過Cluster虛擬IP的方式(如上圖所示,通過IP tables)路由到Pod IP來做服務的路由(除了Cluster IP方式也有的人對於內部連接會采用Ingress方式去做,路由方面會更強大,不過這是不是又類似v0.2了呢?)。當然,我們還可以更進一步引入內部DNS,使用內部域名解析成Cluster IP,客戶端在調用服務的時候直接使用域名(域名可以通過配置服務來配置,也可以直接讀取環境變量)即可。如果這麽幹的話其實就沒有Eureka啥事了,有的公司沒有選擇這種純K8S服務路由的方式還是使用了註冊中心,如果這樣的話其實服務註冊到註冊中心的就是Pod IP,還是由微服務客戶端做服務發現的工作。我更喜歡這種方式,我覺得K8S的服務發現還是弱了一點,而且IP tables的方式讓人沒有安全感(IPVS應該是更好的選擇),與其說是服務發現,我更願意讓K8S只做容器調度的工作以及Pod發現的工作。

雖然K8S可以做一部分服務發現的工作,我們還是需要在客戶端中去實現更多的一些彈力方面的功能,因此我認為2.0時代只是說是微服務框架結合容器、容器調度,而不能是脫離微服務框架本身完全依靠K8S實現微服務。2.0和1.0的本質區別或者說增強還是很明顯,那就是我們可以全局來統籌解決我們的微服務部署和可靠性問題,在沒有容器和容器調度這層抽象之前,有的公司通過實現自動化虛擬機分配拉起,加上自動化初始腳本來實現自動的微服務調度擴容,有類似的意思,但是非常花時間而且速度慢。K8S真正讓OPS成為了DEV而不是執行者,讓OPS站在總體架構的層面通過DEV(咱不能說開發DSL文件不算開發吧)資源和資源之間的關系來統籌整個集群。在只有十幾個微服務若幹臺服務器的小公司可能無法發揮2.0容器雲的威力,但是服務器和服務一多,純手工的命令式配置容易出錯且難以管理,K8S真的釋放了幾十個運維人力。

6、微服務v3.0——ServiceMesh服務網格玩法

技術分享圖片 在之前提到過幾個問題:

  • SOA的模式雖然簡單,但是集中的Proxy在高並發下性能和擴容會是問題
  • 傳統的RPC方式,客戶端很重,做了很多工作,甚至協議轉換都在客戶端做,而且如果涉及到跨語言,那麽RPC框架需要好幾套客戶端和服務端
  • K8S雖然是一個重要的變革,但是在服務調度方面還是太弱了,它的專項在於資源調度

於是ServiceMesh服務網格的概念騰空而出,巧妙解決了這幾個問題:

  • 采用邊車模式的Proxy隨服務本身部署,一服務一邊車與服務共生死(當然,有的公司會使用類似ServiceBus的Global Proxy作為Sidecar Proxy的後備,防止服務活著Sidecar死了的情況)可以解決性能問題
  • Sidecar裏面做了路由、彈力等工作,客戶端裏可以啥都不幹,如上圖所示,上圖是Istio的架構,Istio的理念是把ServiceMesh分成了數據面和控制面,數據面主要是負責數據傳輸,由智能代理負責(典型的組件是Envoy),控制面由三大組件構成,Pilot負責流量管理和配置(路由策略、授權策略)下發,Mixer負責策略和數據上報(遙測),Citadel用於密鑰和證書管理
  • 由於我們雙邊都走Sidecar Proxy,我們對於流量的進出都可以做很細粒度的控制,這個控制力度是之前任何一種模式都無法比擬的,這種架構的方式就像把服務放到了網格之中,服務連接外部的通訊都由網格進行,服務本身輕量且只需要關註業務邏輯,網格功能強大而靈活
  • 對於Proxy的流量劫持可以使用IP table進行攔截,對於服務本身無感知,而且Sidecar可以自動註入Pod,和K8S進行自動整合,無需特殊配置,做到透明部署透明使用
  • Pilot是平臺無關的,采用適配器形式可以和多個平臺做整合,如果和K8S整合的話,它會和API Server進行通訊,訂閱服務、端點的信息,然後把信息轉變成Istio自己的格式作為路由的元數據
  • Mixer期望的是抽象底層的基礎設施,不管是Logging還是Metrics、Tracing,在之前RPC時代的做法是客戶端和服務端都會直接上報信息到InfluxDb、Tracing Server等,這讓客戶端變得很臃腫,Istio的理念是這部分對接後端的工作應該由統一的組件進行,不但使得Proxy可以更輕而且可以通過Plugin機制對接各種後端基礎設施

說了這麽多ServiceMesh的優勢,我們來看一下這種模式的性能問題。想一下各種模式下客戶端要請求服務端整個HTTP請求(跳)次數:

  • 古典模式:2跳,代理轉發一次
  • SOA模式:2跳,總線轉發一次
  • 傳統模式:1跳,客戶端直連服務端
  • K8S Service模式:1跳(路由表會有一定損耗)
  • ServiceMesh模式:3跳(其中2跳是localhost回環)

總的來說,3跳並不是ServiceMesh的瓶頸所在,而更多的可能性是Istio的倔強的架構理念。Istio認為策略和遙測不應該耦合在Sidecar Proxy應該放到Mixer,那麽相當於在調用服務的時候還需要額外增加Mixer的同步請求(來獲得策略方面的放行)。Istio也在一直優化這方面,比如為Mixer的策略在Proxy做本地緩存,為遙測數據做批量上報等等。雖然經過層層優化,但是Istio目前的TPS不足2000,還是和一般的RPC能達到的20000+有著十倍的差距,說不定將來Istio會有架構上的妥協,把Mixer變為非直接依賴,策略方面還是采用類似Pilot統一管理配置下發的方式,遙測方面還是由Sidecar直接上報數據到Mixer。

我個人認為,ServiceMesh是一個非常正確的道路,而且ServiceMesh和K8S結合會更好,理由在於:

  • K8S讓資源調度變得自由,但微服務調度不是其所長也不應該由它深入實現
  • 以Istio為代表的ServiceMesh做了K8S少的,但是微服務又必須的那塊工作
  • Istio的設計方面和K8S極其相似,低耦合,抽象的很好,兩者結合的也很好,我非常喜歡和贊同Agent+統一的資源管理配置下發的方式(K8S的Agent就是KubeProxy和Kubelet,Istio的Agent就是Sidecar Proxy),這是松耦合和高性能的平衡
  • 在復雜的異構環境下,多協議的內部通訊,跨平臺跨語言的內部通訊很常見,如果采用傳統方式,框架太胖太重,把這部分工作從內部剝離出來好處多多

但是,可以看到目前ServiceMesh還不算非常成熟,Istio在不斷優化中,Linkerd 2.x也想再和Istio拼一下,到底誰會勝出還難以知曉,經過之前Dubbo vs Spring Cloud的折騰,Mesos vs K8S的折騰,VM vs Docker的折騰,是否還能經得起折騰Istio vs Linkerd 2呢?我建議還是再看一看,再等一等。

7、暢想Everything Mesh模式?

技術分享圖片 之前看到過ShardingSphere受到ServiceMesh的理念影響提出了DB Mesh的架構。其實DB Proxy的中間件已經存在很多年了(集中化的Proxy類似服務總線的方式),DB Mesh把Proxy也變為輕量的Sidecar方式,DB的訪問也都走本地代理。那麽這裏我也在想,是不是有可能所有東西都有本地的代理呢?

作為應用服務本身而言,只需要和本地代理做通訊調用外部服務、緩存、數據庫、消息隊列,不需要關心服務和資源所在何地,以及背後的實際服務的組件形態。當然,這只是一個暢想了,對於有狀態的資源,Mesh的難度很大,對於類似DB這樣的資源因為調用層次並不復雜,也不太會存在異構場景,Mesh的意義不大,綜合起來看Everything Mesh的投入產出比相比Service Mesh還是小很多。

8、Spring Cloud、K8S和ServiceMesh的關系

如果搞Java微服務的話,Spring Boot是離不開的,但是是否要用Spring Cloud呢?我的觀點是,在目前階段如果沒有什麽更好的選擇,還是應該先用。Spring Cloud和K8S首先並不是矛盾的東西,K8S是偏運維的,主要做資源整合和管理,如果徹底沒有服務治理框架純靠K8S的話會很累,而且功能不完整。開發和架構可以在Spring Cloud方面深耕,運維可以在容器和K8S方面發力,兩套體系可以協作形成目前來說比較好的微服務基石。至於K8S的推行,這一定是一個正確的方向,而且和軟件架構方面的改進工作一點不矛盾,畢竟K8S是脫離於具體語言和平臺的。

至於Service Mesh,它做的事情和Spring Cloud是有很多重復的,在將來Istio如果發展的更好的情況下,應該可以替代Spring Cloud,開發人員只需要用Spring Boot開發微服務即可,客戶端方面也可以很瘦,不需要過多關心服務如何通訊和路由,服務的安全、通訊、治理、控制都由Service Mesh進行(但是,是否有了Sidecar,客戶端真的完全不需要SDK了呢?我認為可能還是需要的,對於Tracing,如果沒有客戶端部分顯然是不完整的,雖然Sidecar是localhost但是還是跨進程了)。

Spring Cloud目前雖然針對K8S和Istio做了一些整合,但是並沒看到一套針對ServiceMesh的最佳實踐出來,是否將來Spring Cloud會在微服務這方面做退化給ServiceMesh讓步還不得而知。總的來說,長期我看好Spring Boot + K8S + Istio的組合,短期我認為還是Spring Boot + K8S + Spring Cloud這麽用著。

9、總結

本文總結了各種微服務落地的形態,由於技術多樣,各種理念層出不窮,造成了微服務的落地方式真的很難找到兩家相同的公司,本文中我們介紹了:

  • 客戶端寫死地址+F5代理的方式
  • 客戶端把地址配置在配置服務+Nginx代理的方式
  • SOA+集中式ESB的方式
  • 傳統的具有註冊中心的服務框架SDK形式
  • 服務框架+K8S方式
  • K8S Service Iptables路由方式
  • ServiceMesh代理3跳轉發方式

當然,可能還會有更多的方式:

  • 內部DNS方式(直接DNS輪詢)
  • K8S內部服務走Ingress方式(內部服務也走Ingress,類似所有服務Nginx代理的方式)
  • ServiceMesh代理2跳轉發方式(可以根據需要跳過遠端的Sidecar來提高性能等等)
  • 瘦服務框架SDK+ServiceMesh方式(也就是還是有一個小的SDK來對接ServiceMesh的Sidecar,而不是讓應用程序自己發揮Http Client,這個方式的好處在於更靈活,這個SDK可以在這一層再做一次路由,甚至在Sidecar出問題的時候直接把流量切換出去,切換為直連遠端或統一的Global Proxy)

也可能很多公司在混用各種方式,具有N套服務註冊中心,正在做容器化遷移,想想就頭痛,微服務的理念層出不窮伴隨著巨頭之間的技術戰役,苦的還是架構和開發,當然,運維可能也苦,2019新年快樂,Enjoy微服務!

歡迎加入QQ群架構華山論劍:836442475(大牛聚集地)一起交流學習探討!



朱曄的互聯網架構實踐心得S2E4:小議微服務的各種玩法