微服務-分解應用程式從而實現更好的部署特性及可伸縮性

黃博文發表於2014-06-08

本文是我翻譯INFQ上的一篇文章。作者Chris由簡入深的講解了微服務的來龍去脈、使用場景、優勢劣勢、以及現有技術棧向微服務架構的重構步驟。是一篇微服務主題的不可多得的好文。

原文地址:http://www.infoq.com/articles/microservices-intro?utm_source=infoq&utm_medium=popular_links_homepage#.U4-QbLLNKmI.gmail

微服務:分解應用程式從而實現更好的部署特性及可伸縮性

本文描述了越來越受歡迎的微服務架構模式(Microservice architecture pattern)。微服務背後的大創意是將大型的、複雜的、長期的應用程式架構為隨時進化的緊密結合的一組服務。術語微服務強烈建議服務應當是微小的。

社群中甚至提倡構建10-100個LOC服務。然而,擁有微小的服務是可取的,但其不應該是主要目的。你應該旨在將你的系統分解為服務,從而解決下面討論的開發及部署問題。一些服務確實應當是微小的,其它的則有可能是相當大的。

微服務架構的本質並不是一個新事物。分散式系統的概念是非常古老的。微服務架構也類似於SOA。

在本文中,你將學習使用微服務架構的動機以及與更傳統的架構-單塊架構(monolithic architecture)的比較。我們討論了微服務的優點和缺點。你將學習如何通過微服務架構來解決一些關鍵的技術挑戰,包括服務間通訊和分散式資料管理。微服務甚至被稱為輕量級的或細粒度的SOA。確實,某種意義上說微服務是非商業化的不能感知WS*和ESB包的SOA。儘管微服務並不是新鮮的玩意,但是仍值得討論,因為它與傳統的SOA是不同的,更重要的是,它解決了許多組織當前遭受的很多問題。

(有時是邪惡的)單塊架構

開發web程式的最早期時間,最被廣泛使用的企業程式架構是將程式的伺服器端元件打包為單個單元。很多企業Java應用程式由單個WAR或EAR檔案組成。其它語言(比如Ruby,甚至C++)編寫的應用程式也大抵如此。

讓我們想象一下,例如你在構建一個線上商店,從客戶那裡獲取訂單,驗證清單及可用的信用卡,然後運送。你構建的程式與圖1所示會非常相似。

微服務-分解應用程式從而實現更好的部署特性及可伸縮性

圖 1單塊架構

該應用程式由好幾個元件組成。包括了儲存前端UI,其實現了使用者介面,和服務一起管理產品分類,處理訂單和管理客戶的賬戶。這些服務共享一個由多個實體組成的領域模型,實體包括產品,定點和客戶等。

儘管該程式擁有一個邏輯清晰的模型設計,但仍是一個單塊架構。例如,如果你是使用Java,則該應用程式將由一個單獨的WAR檔案組成,並且執行在一個web容器中(比如Tomcat)。該程式的Rails版本可能會有一個具有一定層級結構的目錄組成,部署也使用該目錄,比如使用Phusion Passenger部署在Apache/Nginx,或者使用JRuby部署在Tomcat。

這種所謂的單塊架構有一定的優點。單塊架構的應用程式非常容易開發,因為IDE及其它開發工具都適合開發單個應用程式。這些程式也很容易被測試,你只需啟動一個程式即可。單塊架構的應用程式也很容易部署,因為你只需複製開發單元(一個檔案或目錄)到一個執行者相應服務容器的機器即可。

相對而言該方式更適用於小程式。然而,單塊架構在複雜的程式中很難駕馭。一個龐大的單塊程式對於開發者來說很難理解和維護。它對頻繁改動的開發過程來說也是一種阻礙。為了對某個程式元件做修改,你不得不構建和部署整個程式,這相當複雜,風險極大,也比較耗時,需要很多開發者共同協作,還需要較長的測試周期。

單塊架構也使得試用和採用新的技術變得困難。例如,嘗試一個新的基礎設施框架而不重寫整個程式是非常困難的,風險又大又不現實。因此,你經常被專案開始時你做的技術選型阻塞。換句話說,單塊架構對於支援大型的,週期長的應用程式並不具備伸縮性。

將應用程式分解為服務

幸運的是,有其它的具有可伸縮性的架構風格。《The Art of Scalability》一書中描述了真實有用的三維伸縮性模型:伸縮性立方體,如圖2所示。

微服務-分解應用程式從而實現更好的部署特性及可伸縮性

圖2 伸縮性立方體

在該模型中,通過一個負載均衡來執行應用程式的多個完全一樣的副本的方式來實現應用程式伸縮性,這種方式稱為X軸伸縮性。這是一種很好的方式來提高應用程式的容量和可用度。

當使用Z軸伸縮性,每個伺服器執行程式碼的一個完全相同的副本。在該方面,它與X軸伸縮性很相似。最大的不同是每個伺服器只負責資料的一個子集。該系統的一些元件負責將每個請求路由給適當的伺服器。一個常見的路由規則是把請求的一個屬性作為被訪問的實體的主鍵,比如分割槽。另一個常見的路由規則是客戶型別。例如,應用程式可以向付費使用者提供比免費使用者更高的SLA,實現方式是將付費使用者的請求路由到具有更高容量的一組伺服器上。

Z軸伸縮性與X軸伸縮性類似,提高了應用程式的容量和可用度。然而,沒有任何一個方式能夠解決不斷增加的開發工作和程式複雜度的問題。解決這些問題需要Y軸伸縮性。

伸縮性的第三個維度是針對功能性分解的Y軸伸縮性。Y軸伸縮性與Z軸伸縮性分解事情的方式相似但有不同。在應用程式層級,Y軸伸縮性將單塊應用程式分割為一組服務。每個服務實現了一組相關的功能特性,例如訂單管理,客戶管理等。

決定如何將系統分割為一組服務更像是一門藝術,但是可藉助於一些策略。一種方式是通過動詞或使用情況分割服務。例如,接下來你會看到被分割的線上商店有一個結賬UI服務,其實現了結賬用例的UI。

另一個分割方式是通過名詞或資源分割系統。這種服務負責處理給定的實體/資源的所有操作。例如,稍後你將看到為什麼線上商店擁有目錄服務是有道理的,其管理產品的目錄。

理想情況下,每個服務只有一小組職責。Bob Martin(大叔)討論了使用單一職責原則設計類。SRP定義了類的職責為有且只有一個理由被改變。將SRP應用到服務設計中也是有道理的。

另一個有助於服務設計的類似設計是Unix工具的設計。Unix提供了大量的工具,比如grep,cat和find。每個工具只做一件事,效果往往非常好,並且可以使用shell指令碼組合多個工具以執行復雜的任務。在Unix工具中對服務建模並建立單一功能服務很有道理。

強調分解的目標不只是為了擁有微小的(例如,一些主張有10-100 LOC)服務。相反,目標是解決之前討論過的實際問題和單塊架構的侷限性。一些服務應當是微小的,但是其它服務可能更大些。

如果應用Y軸來分解示例程式,我們得到的架構如圖3所示。

微服務-分解應用程式從而實現更好的部署特性及可伸縮性

圖3 微服務架構

分解後的程式由各種各樣的前臺服務和多個後臺服務組成,這些前臺服務實現了使用者介面不同部分。前臺服務包括目錄UI和結賬UI。目錄UI實現了產品搜尋和瀏覽,結賬UI實現了購物車和結賬流程。後臺服務包含了在文章開始時相同的邏輯服務。我們將該應用程式的每個主要的邏輯元件轉換為了獨立的服務。讓我們看看這樣做的後果。

微服務架構的優點和缺點

該架構有一些優點。首先,每個微服務相對較小。開發者很容易理解該程式碼。少量的程式碼不會拖慢IDE,使得開發者更加高效。並且,每個服務比一個大型的單塊程式啟動速度要快的多,這又一次使得開發者更加高效,加快部署過程。

其次,每個服務的部署與其它服務是獨立的。如果某程式設計師只對一個服務負責,並且想要對該服務部署一個改動,只需修改f本地服務而無需其他程式設計師的協作。程式設計師部署修改很簡單。微服務使得持續部署更加可行。

第三,每個服務可通過X軸複製和Z軸分割獨立於其它服務進行擴充套件。此外,每個服務可被部署到最適合該服務的資源要求的硬體上。這與使用單塊架構的情況完全不同,單塊架構中的元件的資源要求是不同的,例如是CPU密集型的還是記憶體密集型的,但是你又必須一起部署。

微服務架構使得開發過程更具擴充套件性。你可以使用多個小型(例如,兩個披薩餅)的團隊進行開發。每個團隊只負責對單個服務或一組相關的服務的開發和部署。每個團隊可獨立於其它的團隊來開發,部署和擴充套件他們的服務。

微服務架構也提升了錯誤隔離。例如,一個服務中的記憶體洩露隻影響該服務。其它服務將會繼續正常的處理請求。對比而言,一個單塊架構的具有錯誤行為的元件會使整個系統崩潰。

最後但不是最重要的一點,微服務架構消除了技術棧任何長期的承諾。原則上來說,當開發一個新的服務時,開發者可以選擇任何適合於當前服務的語言和框架。當然,許多組織團體限制這些選擇也有一定道理,但是關鍵點在於你不受限於過去的決定。

此外,由於服務是微小的,使用其它語言和技術重寫服務也變得更加實用。這也意味著如果嘗試新技術失敗,你只需丟掉這些工作而無需給整個專案帶來風險。這與使用 單塊架構是完全不同的,這裡你最初的技術選擇會嚴格限制未來使用不同的語言和框架的能力。

缺點

當然,沒有任何一項技術是銀彈,微服務也有一些重大的缺點和問題。首先,開發者必須面對建立一個分散式系統的額外的複雜性。開發者必須實現一個程式間通訊機制。不用分散式事務實現跨服務的用例是困難的。IDE和其它的開發工具關注於建立單塊架構的應用程式,並不對開發分散式應用程式提供顯式的支援。編寫引用了多個服務的自動化測試頗具挑戰性。而你使用單塊架構則無需處理這些問題。

微服務架構也引入了重大的操作複雜度。有很多容易變動的部分(不同型別的服務的多個例項)需要在產品環境中管理。要成功實現這點你需要高階別的自動化,無論是自己編寫的程式碼還是類似於PaaS的技術(例如Netfix Asgard)和相關的元件,或者一個現成的PaaS(例如Pivotal Cloud Foundry)。

而且,跨多個服務開發功能要求多個開發團隊間小心翼翼的協作。你需要建立一個展示計劃,該計劃基於服務間依賴情況而制定服務部署順序。這與使用單塊架構的情形非常不同,你只需使用原子操作即可部署更新多個元件。

使用微服務架構的另一個挑戰是在應用程式的那個週期點決定使用該架構。當開發應用程式的第一個版本時,你通常不會遇到該架構能夠解決的問題。此外,使用複雜的分散式架構會拖慢開發速度。

這可能在專案剛開始時陷入左右為難的情況,最大的挑戰經常是如何伴隨著應用程式快速演化業務模型。使用Y軸分割可能會導致快速迭代更加困難。然而,當挑戰變為如何提高可伸縮性時你需要使用功能性分解,但是糾纏不清的依賴使得將單塊應用程式分解為一組服務變得困難。

正因為如此,不能輕易著手採用微服務架構。然而,對於需要高伸縮性的應用程式,比如面向消費者的web程式或SaaS程式,採用微服務架構通常是正確選擇。一些出名的網站,比如eBay,Amazon.com,Groupon和Gilt都已經把單塊架構進化為微服務架構。

現在我們已經知道微服務架構的關鍵設計的優點和缺點,現在開始瞭解程式間和程式與客戶端的通訊機制。

微服務架構中的通訊機制

微服務架構中,應用程式和客戶端通訊的模式,以及應用程式元件間的通訊機制與單塊應用程式是不同的。首先來看應用程式的客戶端與微服務是如何互動的。接下來我們將檢視應用程式內部的通訊機制。

API閘道器模式

在單塊架構中,應用程式的客戶端,比如web瀏覽器和原生應用程式,傳送HTTP請求通過一個負載均衡到N個完全一樣的應用程式例項的其中一個。但在微服務架構中,單塊程式被服務集合替代。結果,我們需要回答的關鍵問題是客戶端應該與什麼互動?

一個應用程式客戶端,比如原生的移動應用程式,可以向單個服務傳送RESTful HTTP請求,如圖4所示。

微服務-分解應用程式從而實現更好的部署特性及可伸縮性

圖4 直接呼叫服務

表面上來看這很有吸引力。然而,在單個服務的API和客戶需要的資料之間可能會有一個顯著的錯誤匹配粒度。例如,顯示一個網頁可能潛在需要呼叫大數量的服務。例如Amazon.com,描述了一些頁面如何需要100+的服務呼叫。即使在高速的網路連線下,更不用說低頻寬,高延遲的行動網路,如此多的請求會非常低效且導致低劣的使用者體驗。

更好的方式是客戶端對每個頁面發出少量的請求,甚至少至一個在網際網路前端伺服器被稱為API閘道器,如圖5所示。

微服務-分解應用程式從而實現更好的部署特性及可伸縮性

圖5 API閘道器

API閘道器位於應用程式的客戶端與微服務之間。它提供了專為客戶端定製的API。API閘道器為移動客戶端提供了粗粒度的API,為桌面客戶端提供了細粒度的API,因為客戶端使用高效能的網路。在本例中,桌面客戶端傳送多個請求來獲取一個產品資訊,而移動客戶端只傳送單個請求。

API閘道器處理接收的請求,將這些請求通過高效能的區域網(LAN)轉發給一定數量的微服務。例如,Netfix描述了每個請求如何平均分給6個後臺服務。在本例中,從桌面客戶端傳送來的細粒度的請求只是被簡單的代理給對應的服務,而從移動客戶端發來的粗粒度的請求處理的方式是組合呼叫多個服務的結果。

API閘道器不僅可以優化客戶端和應用程式間的通訊,也能隱藏微服務的細節。這使得微服務的進化不會影響客戶端。例如,兩個微服務可能會被合併。另一個微服務則可能被分割為兩個或更多的服務。API閘道器唯一需要的做的是更新或反映這些修改。客戶端完全不受影響。

現在已經知道了API閘道器是如何調解應用程式和其客戶端的,現在看看如果實現微服務間的通訊。

服務間通訊機制

使用微服務架構的另一個不同之處是應用程式的元件之間互動方式的不同。單塊應用程式中,元件間呼叫是通過常規的方法呼叫實現的。但是微服務架構中,不同的服務執行於不同的程式。結果,服務間必須使用一個程式間的的通訊(IPC)機制來互動。

同步HTTP

在微服務架構中有兩個主要的方式實現程式間通訊。一種選項是基於同步HTTP的機制,比如REST或SOAP。這是簡單和熟悉的IPC機制。它是防火牆友好的,所以可以穿透網路,而且實現通訊的請求/回覆風格也比較容易。HTTP的低層不支援其它的通訊模式,比如釋出-訂閱模式。

另一個限制是客戶端和伺服器端必須保持同時線上,通常這不能隨時保證,因為分散式系統很容易出現部分故障。而且,HTTP客戶端需要知道伺服器的主機地址和埠。聽起來很簡單,但整個並不簡單,特別是在使用自動擴充套件的雲部署中,這些服務例項是短暫的。應用程式需要使用一種服務發現機制(service discovery mechanism)。一些程式使用一個服務註冊器,比如Apache ZooKeeper或Netflix Eureka。其它的程式中,服務必須註冊到負載均衡器中,比如在Amazon VPC的一個內部的ELB。

非同步訊息機制

同步HTTP的一個替代方案是使用非同步的基於訊息的機制,比如基於AMQP的訊息中介軟體。這種方式有一些優點。它解耦了訊息生產者和訊息消費者。訊息中介軟體將快取訊息直到消費者能夠處理它們。生產者完全不知道消費者的存在。生產者簡單地與訊息中介軟體互動,並且不需要使用服務發現機制。基於訊息的通訊也支援多種通訊模式,比如單向請求和釋出-訂閱。使用訊息的一個缺點是需要一個訊息中介軟體,這是系統容易變動的另一部分,這會增加系統複雜度。另一個缺點是請求/回覆風格的通訊不是天作之合。

兩種方式各有優劣。應用程式可能混合使用這兩種方式。例如,接下來的部分將會討論在分段的架構中如何解決資料管理問題,你將看到如何同時使用HTTP和訊息機制。

分散資料管理

將應用程式分解為服務的結果是資料庫也被分割了。為了保證解耦,每個服務要有自己的資料庫(模式)。此外,不同的服務可以使用不同的資料庫,這被稱為多語言的持久架構。例如,需要ACID事務的服務可能使用關係型資料庫,而操作社交網路的服務可能使用圖形資料庫。分割資料庫是必要的,但有一個新問題要解決:如何處理需要訪問多個服務擁有的資料的請求。先來看如何處理讀請求,再看如何處理更新請求。

處理讀請求

例如,考慮在線上商店中每個客戶有信用額度。當客戶試圖新增訂單時,系統必須驗證所有未結賬單的總價不會超出信用額度。在整體應用程式中實現這種業務邏輯不難。但是如果客戶是由客戶服務管理,而其它部分由訂單服務管理的情況下,在系統中實現登記更困難。訂單服務必須通過某種方式訪問由客戶服務維護的信用額度資訊。

一個解決方案是訂單服務通過一個RPC呼叫向客戶服務獲取信用額度。這種方式很容易實現,而且保證了訂單服務始終拿到的是最新的信用額度。缺點是它降低了可用性,因為客戶服務必須時刻執行來訂貨。由於額外的RPC呼叫也增加了響應時間。

另一種方式是訂單服務儲存信用額度的一份副本。這消除了向客戶服務發請求的需要,從而提高了可用性,減少了響應時間。然而,這意味著我們必須實現一種機制:當客戶服務中的信用額度被修改時,來更新信用額度在訂單服務中的副本。

處理更新請求

保持訂單服務中信用額度一直是最新的問題是一個常見的問題的示例。該問題是如何處理更新被多個服務擁有的資料的請求。

分散式事務

當然,有個解決方案是使用分散式事務。例如,當更新客戶的信用額度時,客戶服務呼叫一個分散式的事務來更新本身的信用額度以及被訂單服務維護的對應的信用額度。使用分散式事務也保證了資料的始終一致性。使用分散式事務的缺點是減少了系統可用性,因為所有參與者都必須可用,以保證事務能夠提交。此外,分散式事務已經失寵,現代的軟體棧(例如REST,NoSQL資料庫等)通常已不支援分散式事務。

事件驅動的非同步更新

另一種方式是使用事件驅動的非同步複製。服務通過釋出事件來宣佈一些資料被修改。其它服務訂閱這些事件來更新各自的資料。例如,當客戶服務更新了一個客戶的信用額度時,它釋出了一個CustomerCreditLimitUpdatedEvent,其包含了客戶id和新的信用額度值。訂單服務訂閱了這些事件並更新自身的信用額度副本。該事件流顯示在圖6中。

微服務-分解應用程式從而實現更好的部署特性及可伸縮性

使用事件複製信用額度

本方式的主要優點是事件的生產者和消費者是解耦的。這不僅簡化了開發,並且與分散式事務相比它提高了可用性。如果消費者無法處理事件,訊息中介軟體會將訊息儲存在佇列中直到消費者可以處理。該方式的主要缺陷是以一致性換可用性。應用程式的編寫方式要能容忍最終一致性資料。開發者也需要實現修正事務來執行邏輯回滾。儘管有此缺陷,但仍不失為許多程式中的最佳方式。

重構單塊架構

不幸的是我們不能總是工作於新品牌的綠色專案。如果你在負責一個大型的可怕的單塊程式的專案中,那是個好機會。每天你都會處理在文章開頭描述過的那些問題。好訊息是有很多你可以使用的技術來分解你的單塊應用程式為一組服務。

首先停止讓問題更糟。不要繼續通過向單塊應用程式新增程式碼的方式來實現新功能。你應當採用某種方式來將新功能實現為獨立的服務,正如圖7所示。這可能並不容易。你可能會編寫凌亂的,複雜的膠水程式碼來向單塊應用程式整合服務。但這是打散單塊程式的第一步。

微服務-分解應用程式從而實現更好的部署特性及可伸縮性

圖7 抽取服務

其次,識別單塊程式的元件並轉換為緊密結合的獨立服務。從元件抽取的好的候選者是不斷改變的元件,或有資源需求衝突的元件,比如大型的記憶體快取或CPU密集型操作。表示層也是另一個好的候選者。然後你可以將該元件轉換為服務並編寫膠水程式碼來與程式的其它部分整合。再一次,這可能很痛苦,但是它使你可以增量遷移到微服務架構。

總結

單塊架構模式是構建企業級應用程式常用的模式。對於小的應用程式它很適用:開發,測試和部署小型的單塊程式相對簡單。但是,對於大型的複雜的應用程式,單塊架構會阻礙開發和部署。如果你經常長期的鎖定你的初始技術選擇,則會使得持續交付變得困難。對於大型的應用程式,更適合適用微服務架構,其將應用程式分解為一組服務。

微服務架構有很多優點。例如,單個服務更容易理解,可以獨立於其它服務來開發和部署。也更容易使用新的語言和技術,因為你可以一次只對一個服務嘗試新技術。微服務架構也有一些顯著的缺點。特別是對那些更復雜,擁有更多變化部分的應用程式。你需要高階別的自動化,比如PaaS,來高效的使用微服務。你也需要在開發微服務時處理一些複雜的分散式資料管理問題。儘管有這些缺點,微服務架構還是更適用於大型的複雜的應用程式,因為可以快速演化,特別是針對SaaS風格的應用程式。

有多種多樣的策略來增量地將單塊應用程式演化為微服務架構。開發者需要將新的功能實現為服務並編寫膠水程式碼來將該服務與單塊應用程式整合。也可以反覆識別可從單塊程式中抽取元件並轉換為服務。演化並不容易,但總比開發和維護一個難駕馭的單塊應用程式要好。

關於作者

Chris Richardson是一個開發者和架構師。他是Java擁護者,JavaOne 搖滾明星以及POJOs in Action一書的作者。該書描述瞭如何使用POJOs和諸如Spring和Hibernate的框架構建企業級Java應用。Chris也是original Cloud Foundry(一個針對Amazon EC2的早期Java PaaS)的創始人。他向組織機構做諮詢從而提高人們的開發和部署技能,比如使用雲端計算,微服務,以及NoSQL。Twitter ID @crichardson。

相關文章