微服務架構
自2014年業界提出“微服務(Microservices)”的概念以來,微服務架構就不斷演進,並且日趨火爆。越來越多的企業擁抱微服務,期望透過微服務的架構來解決大型專案的管理與運維。
那麼什麼是微服務?微服務架構與傳統的SOA架構有什麼區別?何時應該採用微服務架構?如何構建微服務?本章就針對上述提到的問題,來簡單介紹下微服務架構。
什麼是微服務架構
微服務架構(Microservices Architecture,MSA)的出現並非偶然,而是與這個時代的軟體思想、技術工具的發展有著密切的聯絡。比如,將業務功能服務化,是SOA的延續;RESTful等架構的興起,讓我們可以考慮更多輕量化的通訊機制;領域驅動設計指導我們如何分析並模型化複雜的業務;敏捷方法論幫助我們擁抱變化,快速反應;持續整合和持續交付(CI/CD)促使我們構建更快、更可靠、更頻繁的軟體部署和交付能力;虛擬化和容器技術的發展,使我們簡化了部署環境的建立、安裝;DevOps文化的流行以及全棧自治團隊的出現,使得小團隊更加全功能化。這些都是推動微服務架構誕生和發展的重要因素。
實際上,業界對於微服務本身並沒有一個嚴格的定義。James Lewis和Martin Fowler對微服務架構做了如下定義。
簡言之,微服務架構風格就像是把小的服務開發成單一應用的形式,執行在其自己的程序中,並採用輕量級的機制進行通訊(一般是HTTP資源API)。這些服務都是圍繞業務能力來構建的,透過全自動部署工具來實現獨立部署。這些服務可以使用不同的程式語言和不同的資料儲存技術,並保持最小化集中管理。
MSA包含以下特徵。
·元件以服務形式來提供。正如其名,微服務也是面向服務的。
·圍繞業務功能進行組織。微服務更傾向圍繞業務功能對服務結構進行劃分、拆解。這樣的服務,是針對業務領域有著相關完整實現的軟體,它包含使用介面、持久儲存以及對應的互動。因此團隊應該是跨職能的,包含完整的開發技術——使用者體驗、資料庫以及專案管理。
·產品不是專案。傳統的開發模式致力於提供一些被認為是完整的軟體。一旦開發完成,軟體將移交給維護或者實施部門,然後開發組就可以解散了。而微服務要求開發團隊對軟體產品的整個生命週期負責。
這要求開發者每天都關注軟體產品的執行情況,並與使用者聯絡更緊密,同時承擔一些售後支援。越小的服務粒度越容易促進使用者與服務提供商之間的關係。
·強化終端及弱化通道。微服務的應用致力於松耦合和高內聚,它們更喜歡簡單的REST風格,而不是複雜的協議(如WS或者BPEL或者集中式框架),或者採用輕量級訊息匯流排(如RabbitMQ或ZeroMQ等)來發布訊息。
·分散治理。這是與傳統的集中式管理有很大區別的地方。微服務把整體式框架中的元件拆分成不同的服務,在構建它們時將會有更多的選擇。
·分散資料管理。當整體式的應用使用單一邏輯資料庫對資料持久化時,企業通常選擇在應用的範圍內使用一個數據庫。微服務讓每個服務管理自己的資料庫:無論是相同資料庫的不同例項,或者是不同的資料庫系統。
·基礎設施自動化。雲計算,特別是AWS的發展,降低了構建、釋出、運維微服務的複雜性。微服務的團隊更加依賴於基礎設施的自動化,畢竟釋出工作相當無趣。近些年開始火爆的容器技術,諸如Docker也是一個不錯的選擇(有關容器技術以及Docker的內容在後面章節會涉及)。
·容錯性設計。任何服務都可能因為供應商的不可靠而出現故障。
微服務應為每個應用的服務及資料中心提供日常的故障檢測和恢復。
·改進設計。由於設計會不斷更改,微服務所提供的服務應該能夠替換或者報廢,而不是要長久地發展。
微服務架構與SOA架構的區別
微服務架構(MSA)與面向服務架構(SOA)有相似之處,比如,都是面向服務的,通訊大多基於HTTP。通常傳統的SOA意味著大而全的單體架構(Monolithic Architecture)的解決方案。單體架構有時也被稱為“單塊架構”,這種架構風格會讓設計、開發、測試、釋出的難度都增加,其中任何細小的程式碼變更,都將導致整個系統需要重新測試、部署。而微服務架構恰恰把所有服務都打散,設定合理的顆粒度,各個服務間保持低耦合,每個服務都在其完整的生命週期中存活,將互相之間的影響降到最低。SOA需要對整個系統進行規範,而MSA的每個服務都可以有自己的開發語言、開發方式,靈活性大大提升。
單體架構的例子
我們假設在構建一個電子商務應用,應用從客戶處接收訂單,驗證庫存和可用額度,並派送訂單。應用包含多個元件,包括UI元件(用來實現使用者介面),以及一些後臺服務(用於檢測信用額度、維護庫存和派送訂單)。
應用作為一體應用部署。例如,一個Java Web應用執行在Tomcat之類Web容器上,僅包含單個WAR檔案;一個Rails應用使用PhusionPassenger部署在Apache/Nginx上,或者使用JRuby部署在Tomcat上,它都僅包含單個目錄結構。為了伸縮和提升可用性,我們可以在一個負載均衡器下面執行該應用的多份例項。
單體架構的開發、部署和伸縮如圖9-1所示。
這個方案有以下一些好處。
·易於開發。當前開發工具和IDE的目標就是支援這種一體應用的開發。·易於部署。只需要將WAR檔案或目錄結構放到合適的執行環境下。
·易於伸縮。只需要在負載均衡器下面執行應用的多份副本就可以伸縮。
但是,一旦應用變大、團隊增長,這個方案的缺點就更加明顯,缺點如下。
·程式碼庫龐大。巨大的一體程式碼庫可能會嚇到開發者,尤其是團隊的新人。程式碼庫龐大帶來的問題:第一,應用難以理解和修改,開發速度通常會減緩;第二,由於沒有模組硬邊界,模組化會隨著時間的增加而被破壞;第三,程式碼的變更會比較困難,程式碼質量會隨著時間的增加而逐漸下降。這是個惡性迴圈。
圖9-1 單體架構的開發、部署和伸縮
·IDE超載。程式碼庫越大,IDE執行越慢,開發效率越低。
·Web容器超載。應用越大,容器啟動時間越長。因此開發者大量的時間被浪費在等待容器啟動上。這也會影響部署。
·難以持續部署。對於頻繁部署,巨大的單體架構應用也是個問題。為了更新一個元件,你必須重新部署整個應用。這還會中斷後臺任務(如Java應用的Quartz作業),不管變更是否影響這些任務,這都有可能引發問題。未被更新的元件也可能因此不能正常啟動。因此,鑑於重新部署的相關風險會增大,不鼓勵頻繁更新。尤其對使用者介面的開發者來說,因為他們通常需要快速迭代,頻繁重新部署。
·難以伸縮應用。單體架構只能在一個維度伸縮。一方面,它可以透過執行多個副本來伸縮以滿足業務量的增加。某些雲服務甚至可以動態地根據負載調整應用例項的數量。但是另一方面,該架構不能透過伸縮來滿足資料量的增加。每個應用例項都要訪問全部資料,這使得快取低效,並且增大了記憶體佔用和I/O流量。而且,不同的元件所需的資源不同,有些可能是CPU密集型的,另一些可能是記憶體密集型的。單體架構下,我們不能獨立地伸縮各個元件。
·難以調整開發規模。單體應用對調整開發規模也是個障礙。一旦應用達到一定規模,將工程組織分成專注於特定功能模組的團隊通常更有效。比如,我們可能需要UI團隊、會計團隊、庫存團隊等。單體架構應用的問題是它阻礙組織團隊相互獨立地工作,團隊之間必須在開發進度和重新部署上進行協調。對團隊來說也很難改變和更新產品。
·需要對一個技術棧長期投入。單體架構迫使你採用開發初期選擇的技術棧(某些情況下,是那項技術的某個版本)。單體架構下,很難遞增式地採用更新的技術。比如,你選了JVM,除了Java你還可以選擇其他使用JVM的語言,比如Groovy和Scala也可以與Java很好地進行互操作。但是單體架構下,非JVM寫的元件就不行。而且,如果應用使用了後期過時的平臺框架,將應用遷移到更新更好的框架上就很有挑戰性。
還有可能為了採用新的平臺框架,需要重寫整個應用,這樣就太冒險了。
微服務架構正是解決單體架構缺點的替代模式。
微服務架構的例子
一個微服務架構的應用,或是多層架構的,或是六角架構的,並且包含多種型別的元件。
·表示元件(Presentation Components)。響應處理HTTP請求,並返回HTML或JSON/XML(對於Web Service API而言)。
·業務邏輯(Business Logic)。應用的業務邏輯。
·資料庫訪問邏輯(Database Access Logic)。資料訪問物件用於訪問資料庫。
·應用整合邏輯(Application Integration Logic)。訊息層,如基於Spring的整合。
這些邏輯元件分別響應應用中不同的功能模組。
最終微服務架構的解決方案如下。
·透過採用伸縮立方(Scale Cube),特別是y軸方向上的伸縮來架構應用,將應用按功能分解為一組相互協作的服務的集合。每個服務實現一組有限並相關的功能。比如,一個應用可能包含訂單管理服務、客戶管理服務等。
·服務間透過HTTP/REST等同步協議或AMQP等非同步協議進行通訊。
·服務獨立開發和部署。
·每個服務為了與其他服務解耦,都有自己的資料庫。必要時,資料庫間的一致性透過資料庫複製機制或應用級事件來維護。
微服務架構的服務部署如圖9-2所示。
圖9-2 微服務架構的服務部署
這個方案有以下一些優點。
·每個微服務都相對較小。
易於開發者理解。
IDE反應更快,開發更高效。
Web容器啟動更快,開發更高效,並提升了部署速度。
·每個服務都可以獨立部署,易於頻繁部署新版本的服務。
·易於伸縮開發組織結構。我們可以對多個團隊的開發工作進行組織。每個團隊負責單個服務。每個團隊可以獨立於其他團隊開發、部署和伸縮服務。
·提升故障隔離(Fault Isolation)。比如,如果一個服務存在記憶體洩漏,那麼如果只有該服務受影響,其他服務仍然可以處理請求。相比之下,單體架構的一個元件出錯可以拖垮整個系統。
·每個服務可以單獨開發和部署。
·消除了任何對技術棧的長期投入。
這個方案也有一些缺點。
·開發者要處理分散式系統的額外複雜度。·開發者IDE大多是面向構建單體架構應用的,並沒有顯示提供對開發分散式應用的支援。
·測試更加困難。
·開發者需要實現服務間通訊機制。
·不使用分散式事務實現跨服務的用例更加困難。
·實現跨服務的用例需要團隊間的細緻協作。
·生產環境的部署複雜度高,對於包含多種不同服務型別的系統,部署和管理的操作複雜度仍然存在。
·記憶體消耗增加。微服務架構使用N×M個服務例項來替代N個單體架構應用例項。如果每個服務執行在自己獨立的JVM上,通常有必要對例項進行隔離,對這麼多執行的JVM,就有M倍的開銷。另外,如果每個服務執行在獨立的虛擬機器上,那麼開銷會更大。
何時採用微服務架構
微服務使開發變得更簡單、更快捷。以前開發人員耗費時間來搭建環境、熟悉程式碼結構,在微服務的世界裡會簡單許多。但是,微服務帶來了一系列的非功能性需求,比如事務、服務治理(註冊、發現、負載、路由、認證授權、隔離)、監控(日誌、效能監控、告警、呼叫鏈路)、部署、測試等。微服務依賴於“基礎設施自動化”。
微服務不是“銀彈”,何時採用微服務還需考慮企業自身的需求。
在開發應用的初期,我們通常不會遇到採用微服務這種方法來試圖解決問題的情況。而且,使用這個精細、分散式的架構將會拖慢開發進度。對於初創公司,這是個嚴重問題,因為它們的最大挑戰通常是如何快速發展業務模型及相關應用。
另一個挑戰是如何將系統分割為微服務。這是個技術活,但有些策略可能有幫助。一種方法是透過動詞或用例來分隔。比如,之後你將看到分隔後的電子商務應用有個負責派送已完成訂單的派單服務。另一個透過動詞分隔的例子是實現登入用例的登入服務。
另一種分隔方法是透過名稱或資源來分隔系統。這種服務負責對應給定型別的實體/資源的所有操作。比如,之後會發現為何電子商務系統有個庫存服務來跟蹤產品是否在庫存中。
如果你熟悉DDD(領域驅動設計),那麼採用DDD來設計微服務,不但可以降低微服務環境中通用語言的複雜性,而且可以幫助團隊搞清楚領域的邊界,理清上下文邊界。建議將每個微服務都設計成一個DDD限界上下文(Bounded Context),為系統內的微服務提供一個邏輯邊界。
理論上,每個服務應該只承擔很小的職責。Bob Martin講過使用單一職責原則(SRP)來設計類。SRP定義類的職責作為變化的原因,而且類應該只有一個變化的原因。使用SRP來設計服務也是合理的。
另一個有助於服務設計的類是UNIX實用工具的設計方法。UNIX提供了大量的實用工具如grep、cat和find。每個工具只用於做一件事,通常做得非常好,並且可以與其他工具使用shell指令碼組合來執行復雜任務。