本文是我翻譯INFQ上的一篇文章。作者Chris由簡入深的講解了微服務的來龍去脈、使用場景、優勢劣勢、以及現有技術棧向微服務架構的重構步驟。是一篇微服務主題的不可多得的好文。
微服務:分解應用程序從而實現更好的部署特性及可伸縮性
本文描述了越來越受歡迎的微服務架構模式(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。
文章列表