如今微服務倍受關注:文章、博客、社交媒體討論和會議演講。微服務正在迅速朝着加德納技術成熟度曲線(Gartner Hype cycle)的高峰前進。與此同時,也有持懷疑態度的軟件社區人員認為微服務沒什么新鮮可言。反對者聲稱它的思想只是面向服務架構(SOA)的重塑。然而,無論是炒作還是懷疑,不可否認,微服務架構模式具有非常明顯的優勢 — 特別是在實施敏捷開發和復雜的企業應用交付方面。
我們先來看看為什么要考慮使用微服務。
構建單體應用
我們假設,您開始開發一個打車應用,打算與 Uber 和 Hailo 競爭。經過初步交流和需求收集,您開始手動或者使用類似 Rails、Spring Boot、Play 或者 Maven 等平台來生成一個新項目。
該新應用是一個模塊化的六邊形架構,如圖 1-1 所示:
圖 1-1、一個簡單的打車應用
該應用的核心是由模塊實現的業務邏輯,它定義了服務、領域對象和事件。圍繞核心的是與外部世界接口對接的適配器。適配器示例包括數據庫訪問組件、生產和消費消息的消息組件和暴露了 API 或實現了一個 UI 的 web 組件。
盡管有一個邏輯模塊化架構,但應用程序被作為一個單體進行打包和部署。實際格式取決於應用程序的語言和框架。例如,許多 Java 應用程序被打包成 WAR 文件部署在如 Tomcat 或者 Jetty 之類的應用服務器上。其他 Java 應用程序被打包成自包含(self-contained)的可執行 JAR。類似地,Rails 和 Node.js 應用程序被打包為有目錄層次的結構。
以這種風格編寫的應用是很常見的。他們很容易開發,因為我們的 IDE 和其他工具就是專注於構建單體應用。這些應用程序也很容易測試,您可以通過簡單地啟動並使用如 Selenium 測試包來測試 UI 以輕松地實現端到端(end-to-end)測試。單體應用同樣易於部署,你只需拷貝打包好的應用程序到服務器上。您還可以通過運行多個副本和結合負載均衡器來擴展應用。在項目的早期階段,它可以良好運作。
走向單體地獄
不幸的是,這種簡單的方法有很大的局限性。成功的應用有一個趨勢,隨着時間推移而變得越來越臃腫。您的開發團隊在每個沖刺階段都要實現更多的用戶需求,這意味着需要添加許多行代碼。幾年之后,小而簡單的應用將會逐漸成長成一個龐大的單體。為了給出一個極端示例,我最近和一位開發者做了交談,他正在編寫一個工具,該工具用於從他們的數百萬行代碼(lines of code,LOC)應用中分析出數千個 JAR 之間的依賴。我相信這是大量開發者在多年齊心協力下創造出了這樣的野獸。
一旦您的應用程序成為了一個龐大、復雜的單體,您的開發組織可能會陷入了一個痛苦的境地,敏捷開發和交付的任何一次嘗試都將原地徘徊。一個主要問題是應用程序實在非常復雜,其對於任何一個開發人員來說顯得過於龐大,這是可以理解的。最終,正確修復 bug 和實現新功能變得非常困難而耗時。此外,這種趨勢就像是往下的螺旋。如果基本代碼都令人難以理解,那么改變也不會變得正確,您最終得到的將是一個巨大且不可思議的大泥球。
應用程序的規模也將減緩發展。應用程序越大,啟動時間越長。我調查過開發者們的單體應用的大小和性能,一些報告的啟動時間為 12 分鍾。我也聽說過應用程序啟動需要 40 分鍾以上的怪事。如果開發人員經常要重啟應用服務器,那么很大一部分時間都是在等待中度過,他們的生產力將受到限制。
另一個大問題是,復雜的單體應用本身就是持續部署的障礙。如今,SaaS 應用發展到了可以每天多次將變更推送到生產環境。這對於復雜的單體來說非常困難,因為您需要重新部署整個應用程序才能更新其中任何一部分。聯想到我之前提到的漫長啟動時間,這也不會是什么好事。此外,因變更所產生的影響通常不是很明確,您很可能需要做大量的手工測試。因此,持續部署是不可能做到的。當不同模塊存在資源需求沖突時,單體應用可能難以擴展。例如,一個模塊可能會執行 CPU 密集型圖像處理邏輯,理想情況下是部署在 Amazon EC2 Compute Optimized 實例中。另一個模塊可能是一個內存數據庫,最適合部署到 EC2 Memory-optimized 實例。然而,由於這些模塊被部署在一起,您必須在硬件選擇上做出妥協。
單體應用的另一個問題是可靠性。因為所有模塊都運行在同一進程中。任何模塊的一個 bug,比如內存泄漏,可能會拖垮整個進程。此外,由於應用程序的所有實例都是相同的,該錯誤將影響到整個應用的可用性。
最后但同樣重要,單體應用使得采用新框架和語言變得非常困難。例如,我們假設您有 200 萬行代碼使用了 XYZ 框架編寫。如果使用較新的 ABC 框架來重寫整個應用,這將非常昂貴(在時間和成本方面),即使框架非常好。因此,這對於采用新技術是一個非常大的障礙。在項目開始時,您無論選擇何種新技術都會感到困擾。
總結一下:您有一個成功的關鍵業務應用程序,它已經發展成為一個只有少數開發人員(如果有的話)能夠理解的巨大單體。它使用了過時、非生產性技術編寫,這使得招聘優秀開發人員變得非常困難。應用程序變得難以擴展,不可靠。因此敏捷開發和應用交付是不可能的。
那么您能做些什么呢?
微服務 — 解決復雜問題
許多如 Amazon、eBay 和 Netflix 這樣的組織,已經采用現在所謂的微服務架構模式解決了這個問題,而不是構建一個臃腫的單體應用。它的思路是將應用程序分解成一套較小的互連服務。一個服務通常實現了一組不同的特性或功能,例如訂單管理、客戶管理等。每一個微服務都是一個迷你應用,它自己的六邊形架構包括了業務邏輯以及多個適配器。一些微服務會暴露一個供其他微服務或應用客戶端消費的 API。其他微服務可能實現了一個 web UI。在運行時,每個實例通常是一個雲虛擬機(virtual machine,VM)或者一個 Docker 容器。例如,前面描述的系統可能分解成如圖 1-2 所示:
圖 1-2、一個單體應用分解成微服務
應用程序的每個功能區域現在都由自己的微服務實現。此外,Web 應用程序被划分為一組更簡單的應用。例如,以我們的出租車為例,一個是乘客的應用,一個是司機的應用。這使得它更容易地為特定的用戶、司機、設備或者專門的用例部署不同的場景。每個后端服務暴露一個 REST API,大部分服務消費的 API 由其他服務提供。例如,Driver Management 使用了 Notification 服務器來通知可用司機一個可選路程。UI 服務調用了其他服務來渲染頁面。服務也可以使用異步、基於消息的通信。本電子書后面將會更加詳細介紹服務間通信。
一些 REST API 也暴露給移動端應用以供司機和乘客使用。然而,應用不能直接訪問后端服務。相反,他們之間的通信是由一個稱為 API 網關(API Gateway)的中介負責。API 網關負責負載均衡、緩存、訪問控制、API 計量和監控,可以通過使用 NGINX 來實現。第二章將詳細討論 API 網關。
圖 1-3、開發和交付中的伸縮立方(Scale Cube)
微服務架構模式相當於此伸縮立方的 Y 軸坐標,此立方是一個來自《架構即未來》的三維伸縮模型。另外兩個坐標軸是由運行多個相同應用程序副本的負載均衡器組成的 X 軸坐標和 Z 軸坐標(或數據分區),其中請求的屬性(例如,一行記錄的主鍵或者客戶標識)用於將請求路由到特定的服務器。應用程序通常將這三種類型的坐標方式結合一起使用。Y 軸坐標將應用分解成微服務,如圖 1-2 所示。
在運行時,X 坐標軸上運行着服務的多個實例,每個服務配合負載均衡器以滿足吞吐量和可用性。某些應用程序也有可能使用 Z 坐標軸來進行分區服務。圖 1-4 展示了如何用 Docker 將 Trip Management 服務部署到 Amazon EC2 上運行。
圖 1-4、使用 Docker 部署 Trip Management 服務
在運行時,Trip Management 服務由多個服務實例組成,每個服務實例是一個 Docker 容器。為了實現高可用,容器是在多個雲虛擬機上運行的。服務實例之前是一個類似 NGINX 的負載均衡器,用於跨實例分發請求。負載均衡器也可以處理其他問題,如緩存、訪問控制、API 度量和監控。
微服務架構模式明顯影響到了應用程序與數據庫之間的關系,與其他共享單個數據庫模式(schema)服務有所不同,其每一個服務都有自己的數據庫模式。一方面,這種做法與企業級數據庫數據模型的想法相背,此外,它經常導致部分數據冗余。然而,如果您想從微服務中受益,每一個服務都應該有自己的數據庫模式,因為它能實現松耦合。圖 1-5 展示了數據庫架構示例應用程序。
每個服務都擁有各自的數據庫。而且,服務可以使用一種最適合其需求、號稱多語言持久架構(polyglot persistence architecture)的數據庫。例如,Driver Management,要找到與潛在乘客接近的司機,就必須使用支持高效地理查詢的數據庫。
圖 1-5、打車應用的數據庫架構
從表面上看,微服務架構模式類似於 SOA。微服務是由一組服務組成。然而,換另一種方式去思考微服務架構模式,它是沒有商業化的 SOA,沒有集成 Web 服務規范(WS-*)和企業服務總線(Enterprise Service Bus,ESB)。基於微服務的應用支持更簡單、輕量級的協議,例如,REST,而不是 WS-*。他們也盡量避免使用 ESB,而是實現微服務本身具有類似 ESB 的功能。微服務架構也拒絕了 SOA 的其他部分,例如,數據訪問規范模式概念。
微服務的優點
微服務架構模式有許多非常好的地方。
第一,它解決了復雜問題。它把可能會變得龐大的單體應用程序分解成一套服務。雖然功能數量不變,但是應用程序已經被分解成可管理的塊或者服務。每個服務都有一個明確定義邊界的方式,如遠程過程調用(RPC)驅動或消息驅動 API。微服務架構模式強制一定程度的模塊化,實際上,使用單體代碼來實現是極其困難的。因此,使用微服務架構模式,個體服務能被更快地開發,並更容易理解與維護。
第二,這種架構使得每個服務都可以由一個團隊獨立專注開發。開發者可以自由選擇任何符合服務 API 契約的技術。當然,更多的組織是希望通過技術選型限制來避免完全混亂的狀態。然而,這種自由意味着開發人員不再有可能在這種自由的新項目開始時使用過時的技術。當編寫一個新服務時,他們可以選擇當前的技術。此外,由於服務較小,使用當前技術重寫舊服務將變得更加可行。
第三,微服務架構模式可以實現每個微服務獨立部署。開發人員根本不需要去協調部署本地變更到服務。這些變更一經測試即可立即部署。比如,UI 團隊可以執行 A|B 測試,並快速迭代 UI 變更。微服務架構模式使得持續部署成為可能。
最后,微服務架構模式使得每個服務能夠獨立擴展。您可以僅部署滿足每個服務的容量和可用性約束的實例數目。此外,您可以使用與服務資源要求最匹配的硬件。例如,您可以在 EC2 Compute Optimized 實例上部署一個 CPU 密集型圖像處理服務,並且在 EC2 Memory-optimized 實例上部署一個內存數據庫服務。
微服務的缺點
就像 Fred Brooks 大約在 30 年前寫的《人月神話》中說的,沒有銀彈。與其他技術一樣,微服務架構模式也存在着缺點。
其中一個缺點就是名稱本身。微服務這個術語的重點過多偏向於服務的規模。事實上,有些開發者主張構建極細粒度的 10 至 100 LOC(代碼行) 服務,雖然這對於小型服務可能比較好,但重要的是要記住,小型服務只是一種手段,而不是主要目標。微服務的目標在於充分分解應用程序以方便應用敏捷開發和部署。
微服務另一個主要缺點是由於微服務是一個分布式系統,其使得整體變得復雜。開發者需要選擇和實現基於消息或者 RPC 的進程間通信機制。此外,由於目標請求可能很慢或者不可用,他們必須要編寫代碼來處理局部故障。雖然這些並不是很復雜、高深,但模塊間通過語言級方法/過程調用相互調用,這比單體應用要復雜得多。
微服務的另一個挑戰是分區數據庫架構。更新多個業務實體的業務事務是相當普遍的。這些事務在單體應用中的實現顯得微不足道,因為單體只存在一個單獨的數據庫。在基於微服務的應用程序中,您需要更新不同服務所用的數據庫。通常不會選擇分布式事務,不僅僅是因為 CAP 定理。他們根本不支持如今高度可擴展的 NoSQL 數據庫和消息代理。您最后不得不使用基於最終一致性的方法,這對於開發人員來說更具挑戰性。
測試微服務應用程序也很復雜。例如,使用現代框架如 Spring Boot,只需要編寫一個測試類來啟動一個單體 web 應用程序並測試其 REST API。相比之下,一個類似的測試類對於微服務來說需要啟動該服務及其所依賴的所有服務,或者至少為這些服務配置存根。再次聲明,雖然這不是一件高深的事情,但不要低估了這樣做的復雜性。
微服務架構模式的另一個主要挑戰是實現了跨越多服務變更。例如,我們假設您正在實現一個變更服務 A、服務 B 和 服務 C 的需求,其中 A 依賴於 B,且 B 依賴於 C。在單體應用程序中,您可以簡單地修改相應的模塊、整合變更並一次性部署他們。相反,在微服務中您需要仔細規划和協調出現的變更至每個服務。例如,您需要更新服務 C,然后更新服務 B,最后更新服務 A。幸運的是,大多數變更只會影響一個服務,需要協調的多服務變更相對較少。
部署基於微服務的應用程序也是相當復雜的。一個單體應用可以很容易地部署到基於傳統負載均衡器的一組相同服務器上。每個應用程序實例都配置有基礎設施服務的位置(主機和端口),比如數據庫和消息代理。相比之下,微服務應用程序通常由大量的服務組成。例如,據 Adrian Cockcroft 了解到,Hailo 擁有 160 個不同的服務,Netflix 擁有的服務超過 600 個。
每個服務都有多個運行時實例。還有更多的移動部件需要配置、部署、擴展和監控。此外,您還需要實現服務發現機制,使得服務能夠發現需要與之通信的任何其他服務的位置(主機和端口)。比較傳統麻煩的基於票據(ticket-based)和手動的操作方式無法擴展到如此復雜程度。因此,要成功部署微服務應用程序,需要求開發人員能高度控制部署方式和高度自動化。
一種自動化方式是使用現成的平台即服務(PaaS),如 Cloud Foundry。PaaS 為開發人員提供了一種簡單的方式來部署和管理他們的微服務。它讓開發人員避開了諸如采購和配置 IT 資源等煩惱。同時,配置 PaaS 的系統人員和網絡專業人員可以確保達到最佳實踐以落實公司策略。自動化微服務部署的另一個方式是開發自己的 PaaS。一個普遍的起點是使用集群方案,如 Kubernetes,與 Docker 等容器技術相結合。
總結
構建復雜的微服務應用程序本質上是困難的。單體架構模式只適用於簡單、輕量級的應用程序,如果您使用它來構建復雜應用,您最終會陷入痛苦的境地。微服務架構模式是復雜、持續發展應用的一個更好的選擇。盡管它存在着缺點和實現挑戰。
我的筆記
1. 關於高可用、高並發、高性能:屬於分布式框架設計需要考慮的點。高可用:保證系統一直可用;高並發:保證系統能被同時並行處理很多請求;高性能:指程序處理速度非常快,所占內存少,cpu占用率低。
2. 關於微服務架構的數據庫:在微服務架構中,每一個服務有自己的數據庫,所以我們單個數據庫中部分數據的外鍵取消了,導致不同數據庫中的數據相互使用時必須重復數據,導致部分數據冗余。
3.三個名詞:IaaS、PaaS、SaaS 即雲計算的三種服務模式
- SaaS:Software-as-a-Service(軟件即服務)提供給客戶的服務是運營商運行在雲計算基礎設施上的應用程序,用戶可以在各種設備上通過客戶端界面訪問,如瀏覽器。消費者不需要管理或控制任何雲計算基礎設施,包括網絡、服務器、操作系統、存儲等等;
- PaaS:Platform-as-a-Service(平台即服務)提供給消費者的服務是把客戶采用提供的開發語言和工具(例如Java,python, .Net等)開發的或收購的應用程序部署到供應商的雲計算基礎設施上去。
- IaaS: Infrastructure-as-a-Service(基礎設施即服務)提供給消費者的服務是對所有計算基礎設施的利用,包括處理CPU、內存、存儲、網絡和其它基本的計算資源,用戶能夠部署和運行任意軟件,包括操作系統和應用程序。
4.CAP定理:CAP 理論為:一個分布式系統最多只能同時滿足一致性(Consistency)、可用性(Availability)和分區容錯性(Partition tolerance)這三項中的兩項。
- 一致性(Consistency):一致性指 “all nodes see the same data at the same time”,即更新操作成功並返回客戶端完成后,所有節點在同一時間的數據完全一致。
- 可用性(Availability):可用性指“Reads and writes always succeed”,即服務一直可用,而且是正常響應時間。
- 分區容錯性(Partition tolerance):分區容錯性指“the system continues to operate despite arbitrary message loss or failure of part of the system”,即分布式系統在遇到某節點或網絡分區故障的時候,仍然能夠對外提供滿足一致性和可用性的服務。