十幾年前就有一些公司開始踐行服務拆分以及SOA,六年前有了微服務的概念,於是大家開始思考SOA和微服務的關係和區別。最近三年Spring Cloud的大火把微服務的實踐推到了高潮,而近兩年K8S在容器編排的地位確定之後大家又開始實踐起以K8S為核心的雲原生思想和微服務的結合如何去落地,2018年又多出一個ServiceMesh服務網格的概念,大家又在思考如何引入落地ServiceMesh,ServiceMesh和K8S以及Spring Cloud的關係如何等等。
確實有點亂了,這一波又一波的熱潮,幾乎每兩年都會來一波有關微服務架構理念和平臺,許多公司還沒完成微服務的改造就又提出了服務+容器道路,又有一些公司打算從微服務直接升級成ServiceMesh。本文嘗試總結一下我見過的或實踐過的一些微服務落地方式,並且提出一些自己的觀點,希望拋磚引玉,大家可以暢談一下自己公司的微服務落地方式。
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微服務!