原文鏈接:Introduction to Microservices
微服務現在受到了大量的關注︰ 文章、 博客、 社交媒體和學術會議上的討論都能看到該詞匯的身影。微服務正迅速走向 Gartner Hype cycle 所指的快速發展期。同時,軟件社區的一些懷疑論者指出微服務并不是什么新鮮玩意兒。這些唱反調的人說微服務和SOA概念并沒有什么不同,舊瓶裝新酒而已,順勢炒炒新概念。然而,不管說是夸大也好,懷疑也罷,微服務架構模式應用在敏捷開發和交付復雜的企業應用程序的時候還是有巨大優勢的 。
本博客是設計、構建和部署微服務七篇博客系列的開篇。通過該文章將學到一些微服務的方法,還有微服務架構與傳統單體式架構模式的比較。本系列將介紹微服務架構的各種元素,并揭示該架構模式的各種優點與缺點,以此來指導微服務是否適合您的項目,以及如何運用該模式。
讓我們首先看看為什么你應該考慮使用微服務。
構建單體式應用
假設現在我們為了與Uber和Hailo競爭來構建一個全新在線打車軟件:經過一系列的預備會議和需求收集,我們決定無論是人工擼還是用Rails,Spring Boot,Play,或Maven之類工具生成也好,最終要創建一個全新的應用!應用應該有如下圖六邊形一樣的模塊結構
應用程序的核心是業務邏輯,它了定義服務、 領域對象和事件模塊。圍繞核心是接口與外部世界的適配器。適配器的例子包括數據庫訪問組件、 生產消息和消費消息的消息傳遞組件以及暴露API或實現用戶界面的 web 組件。
盡管在邏輯上模塊化,整個應用還是做為一個巨大的整體進行打包和部署的。而且模塊的結構往往與選擇的編程語言和框架緊密相關。 舉個栗子: 多數java程序打成war包部署到Tomcat 、Jetty等應用服務器中;還有一部分被打成自包含的jar包(Spring Boot支持把jetty或者tomcat包進去,以main為入口啟動應用); 同樣的,Rails 和 Node.js 應用直接以目錄結構的方式部署.
這種風格開發的應用是非常常見的。我們的IDE和工具致力于構建這樣的單一應用,所以我們開發起來也感覺到簡單;此類應用測試起來也很方便,你可以很簡單的啟動這個應用,采用selenium測試UI去完成端到端的測試;單體應用部署起來也不難,把包一拷貝扔服務器里就妥了, 如果要擴展應用,只需要在負載均衡器后面跑多個相同的應用就可以了。工程的初期呢,這個方法工作的還是不錯的!
邁向單體的地獄
很不幸的是,這種方式還是有巨大的局限性的.。一個成功的應用總會隨著時間逐步成長并變得巨大起來。每個敏捷Sprint周期,開發者會實現更多的功能,當然這就意味著又添加了很多行代碼。幾年之后你會發現,當年你寫的那個簡單的小小的應用居然變成一個巨大的單體怪物。舉一個比較極端的例子吧:最近我和一個開發者聊天,他正在寫一個工具來分析數百萬行代碼的巨大應用中的上千個jar的依賴關系。別的不說,寫出這么巨大的怪物肯定花費了很多程序員幾年的時間。
一旦應用變成了巨大復雜的單體,那你的開發團隊將痛苦不堪!敏捷開發和交付的一些愿景在這個龐然大物面前都會受挫。最主要的問題就是這玩意兒現在太復雜了!復雜到很難有某個程序員能完全理解它!導致結果就是,正確的修個bug、做個新功能變的費時又費力。更可恨的是,這是一個惡性循環的過程,應用正在逐步的‘死去’ 。如果你的代碼庫別人都理解不了,那么對代碼做點正確的改動是很難的,最終這個怪物更加的可怕更加的難以理解,糾結!
應用程序的規模大了也會拖慢程序的開發: 程序越大,啟動越慢!調查顯示一些程序員說他們啟動程序要12分鐘,我個人還聽過有的應用啟動要40分鐘!程序員開發過程需要周期性的重啟應用,這樣就浪費了很多時間,效率自然也很低下,不能忍!
巨大、復雜的單體應用還是持續交付的巨大障礙。現在SaaS應用的宗旨是如果有改動能夠每天部署多次。單體應用的中某部分的改動需要需要重新部署整個應用,這樣的話持續交付是相當困難的。前面也講了,啟動一次就要那么久 !另外,改動造成的影響也不是很好被理解,需要大量的手工測試去保證,這樣的話持續交付更是難上加難了!
單體應用在多個模塊對資源需求上有沖突時很難進行擴展。比如:一個新模塊可能實現了CPU密集型的圖片處理邏輯,更適合部署到Amazon EC2 Compute Optimized instances。另一個模塊可能需要一個內存數據庫,更適合使用EC2 Memory-optimized instances。然而,這些模塊必須被一起部署的話,選擇硬件的時候就要好好折中一下嘍。
單體應用的另一個問題是穩定性:因為所有的模塊都跑在同一進程之中,某模塊中的bug,比如內存泄漏是可能拖垮整個應用的!更重要的是,因為負載均衡后面的所有的應用是一樣的,bug還可能影響整個應用的可用性!
最后重要的一點:單體應用很難擁抱新的框架和編程語言。假設你有2百萬行用XYZ框架寫的代碼,如果用ABC框架重寫它將會耗費巨大的時間和金錢!哪怕大家都知道用新框架更適合一些(攤手)。這樣導致的結果是,在嘗試轉換新框架新技術的時候存在巨大的障礙,我們不得不繼續在選型之初定好的技術架構上前行(哭臉)。
最后總結下吧,你從擁有一個業務清晰,幾個程序員都明白的小程序,逐步的變成了一個可怕的單體應用。回首看這個應用是采用了過時的、效率低下的技術來寫的(畢竟技術在進步,抱著老東西悶頭陶醉不是什么好事情)!別的不說,招聘都費勁吶!這個單體應用變的難以擴展、變得極不穩定,想敏捷開發?想持續交付? 恐怕只能呵呵了。
面對這些,你能做點什么呢?
微服務 – 處理這些復雜問題!
很多機構,比如Amazon、eBay、Netflix,已經開始通過擁抱微服務架構去解決上述的問題了。她們不再去構建一個可怕的單體應用,而是拆分成多個更小的、相互連接的微服務!
服務通常實現了一系列相互獨立的功能或特性,比如訂單管理、客戶管理等等。每一個微服務都具有自身業務邏輯以及各種適配器構成的六角形架構模式,都是一個迷你的小應用。有的微服務會暴露API供其他微服務和客戶消費,其他微服務則可能實現Web UI。運行時,每一個實例通常是云平臺的一個VM或者一個Docker容器。
下面是對上述老架構的一個拆分:
應用的每個功能區域現在都以微服務的方式來實現了,此外整個應用被拆分成一系列更小的web應用(比如乘客管理、司機管理)。拆分之后更加方便的為特殊用戶、設備或者特殊用例進行部署。
每一個后端服務都暴露REST API,絕大部分服務也會消費其他服務提供的API。比如,司機管理服務會使用通知服務告知空閑的司機潛在的行程(來生意了,接單!);還有UI 服務會調用其他服務后渲染web頁面。服務之間也會使用異步的,基于消息的通信模式,微服務進程間的通信將會在后面的文章詳述。
一些REST API 也會暴露給移動端設備方便司機和乘客的使用。當然,這些應用不會直接訪問后端各個微服務 ,而是通過一個協調者API網關來訪問的。 API網關的職責有負載均衡、緩存、訪問控制、API計費、監控···,這些事情使用NGINX就OK啦。后面的文章將會講述API網關的細節。
微服務架構模式很契合上圖的Y軸擴展方式,上圖X軸的擴展表示橫向擴展應用,一般就是負載均衡器后面部署幾個相同的應用。Z軸擴展表示數據分區,將請求路由到特定的服務器上(比如數據庫分庫分表,根據主鍵訪問到用戶記錄對應的數據庫)。
應用一般都會三個維度去擴展。Y軸正是按照我們第一節講到的,把單體應用拆分為微服務,運行時呢,為了某個服務達到高吞吐和高可用性,可以采用負載均衡,也就是X軸擴展了,特殊應用也會使用Z軸擴展來切分服務。下面的圖展示了行程管理采用Docker鏡像部署到Amazon EC2的情況:
運行時,行程管理由多個服務實例組成。每個應用實例實際是一個Docker容器。為了達到高可用,容器會跑在多個云平臺VM上,服務實例之前是NGINX這樣的負載均衡器來分發請求,負載均衡器也會關注比如緩存、權限訪問、服務計費、監控等事宜。
微服務架構模式極大的影響應用和數據庫之間的關系。每個微服務都會有自己的數據庫Schema,而不是和其他服務共享一套數據庫Shcema。另一個角度看,這種方式對于以往企業級范圍的數據模型來講是很奇怪的,另外,微服務這么搞會造成數據冗余。然而,如果你想從微服務架構獲益,那么每個微服務一個數據Schema是很有必要的,因為微服務提倡的就是松耦合啊,下面的圖展示了微服務架構下應用的數據架構:
每個微服務都有自己的數據庫,而且每個服務還能選擇適合自己需求的數據庫,這就是所謂的多態型持久化架構。比如,司機如果需要查找附近的潛在乘客,那就必須要使用支持高效地理位置查詢的數據庫。
表面上看微服務和SOA架構很相似,兩種架構都倡導由一系列的服務組成。我們可以把微服務架構模式當成是沒有商業web service規范(WS規范咯)以及ESB等打包套件約束的SOA。基于微服務架構的應用更傾向于簡單的、輕量級的REST協議而不是老舊的WS,當然,微服務也會避免去使用笨重的ESB套件而更喜歡去實現一些ESB部分功能的輕量級工具(不要捆綁,不要全家桶!),微服務架構模式也避免SOA中諸如規范化Schema的定義。
微服務的優勢
微服務架構模式有很多重要的優勢。首先,通過把可怕巨大的單體拆分為一系列的服務解決了單體復雜度問題, 拆分后整體功能數量并沒有變化,但是應用現在變成了多個方便管理的小應用,每個服務都有一個定義良好的服務邊界,通過RPC方式或消息驅動暴露API。微服務架構模式解決了單體應用代碼庫在實踐中極難模塊化的難題,拆分后的微服務能夠更快的部署,更易理解和維護!
第二,拆分后的服務可以被專注于某部分服務的團隊獨立開發。程序員可以基于API約定前提下自由選擇合適的技術,當然,很多機構為了避免技術選擇混亂也會限制技術的選項。因此,自由意味著程序員可以不再局限于項目初期選擇的技術了(撒花)!開始寫一個新的微服務的時候,開發者可以使用一些當下流行的技術,更重要的是,因為每個服務現在拆分的很小,使用現有技術重寫老的服務成為可能!
第三,微服務架構模式使得獨立部署成為可能,開發者不用再忙碌于協調其他模塊的變化再去部署(單體應用,改一個部分可能對其他部分影響,某個更改可能涉及多個模塊的協調),微服務的更改可以在測試后盡快的被部署。比如,UI組可以進行A/B測試并且快速迭代而不用等待整個應用部署。微服務架構模式使得持續部署成為可能!
最后,微服務架構模式使得每個服務可以獨立的擴展。我們可以針對某些有容量和可用性要求的微服務進行擴展,為需要的服務部署多個實例而不是復制多個單體去折中獲得某項提升!更重要的是,我們可以針對服務需求使用最合適的硬件資源。比如,我們可以把CPU密集型的圖片處理服務部署到EC2 Compute Optimized instances,部署內存數據庫相關的服務到EC2 Memory-optimized instances。
微服務的劣勢
正如Fred 30年前的書中所說,“沒有銀彈!”。和其他技術一樣,微服務架構也有其劣勢 。劣勢之一就是它的名字(捂臉),微服務這個詞過分強調了服務的尺寸,實際上還真有幾個開發者號召大家寫10-100行代碼的‘微服務’(這應該不太可能吧)!然而微服務更想表達的是一種工具和途徑,微并不是最終的目標(為微服務而微服務,敲響警鐘),微服務是為了便利敏捷開發和部署而去有效的拆分應用。
微服務另一個劣勢是因為從單體應用拆分為分布式系統帶來的復雜。開發者需要選擇或者實現基于消息或者RPC模式的進程間通訊機制,另外開發者也要寫額外的代碼去處理對于目的服務請求可能存在的請求緩慢或者請求不可用導致的局部故障問題。分布式系統可不是什么火箭科學,它可比單體應用中模塊間通過語言層面或者進程內調用復雜的多了!
微服務另一個挑戰就是拆分數據庫架構。一個邏輯事務更新多個業務記錄是很常見的事情,單體應用實現該事務是比較簡單的,畢竟大家使用一個數據庫。然而在微服務架構中,你必須要更新多個服務的多個數據庫。一般不會使用分布式事務,不僅僅是因為CAP理論,還因為一些流行的NoSQL數據庫和Message Queue系統壓根也不支持(攤手)。最后還得繞回最終一致性方案,這個方案對開發者來講也是略有挑戰的(實際上我看這個系列的文章并且開始翻譯也是因為實際中遇到了這個問題,下面的文章將會提到)。
測試微服務架構的應用也是更加復雜的。比如,使用Spring Boot這種框架,開發者啟動一個單體應用去寫一些測試用例、測試其REST API是很平常事情。相反的是,相類似的測試在微服務中的話,你要啟動或者至少去mock其依賴的其他的服務才能完成。再次強調,微服務不是火箭科學,很重要的一點是大家不要低估了這樣做的復雜度!
微服務架構模式的另一巨大挑戰是實現跨服務的改動。比如,讓我們假設你要實現一個需要更改服務A、B、C的功能,而此時A依賴于B,B依賴于C。在傳統單體應用中,你可以很簡單的去更改對應的模塊,集成這些變動然后一起部署它們。然而在微服務架構模式下,你需要小心翼翼的計劃和協調每個服務的改動和發布:你需要更新服務C,再更新服務B,然后還要更新服務A。幸運的是,絕大部分時候一個服務的變化僅會影響一個服務,多個服務間需要協調的情況是很少出現的。
部署一個基于微服務架構的應用是更加復雜的。一個傳統單體應用可以簡單的部署到負載均衡器后面,因為應用都是相同的,拷貝部署就可以了。每個應用配置好數據庫和message broker的地址(主機和端口)就好了。相對應的微服務一般是大量的服務組成的,比如,Hailo存在160個不同的服務,Netflix則有多達600多個不同服務,每個服務還有多個運行實例!將會有更多的變化的部分需要去配置、部署、擴展、監控。此外還需要實現一個服務發現機制讓其他服務找到它需要通信的服務的地址。傳統的基于Ticket和手動運維的方式是不可能處理好這個級別的復雜擴展的。因此,成功部署一個微服務需要開發者對部署方法有更多控制還要對服務執行高水平的自動化。
自動化的其中一種方式是使用現有的Paas平臺(比如Cloud Foundry)。PaaS平臺為開發者提供了便捷的方式去部署他們的微服務,避免了是他們糾纏于購買和配置IT資源的泥潭。同時,配置PaaS系統和網絡的專業人員可以確保遵守了最佳實踐和公司策略。另一種自動化方式就是開發自己的PaaS平臺。一個典型的開端就是先使用集群方案(比如Kubernetes)配合上Docker容器技術,后續的文章將會介紹基于軟件的應用交付方案,比如NGINX是如何在微服務層面方便的處理緩存、訪問控制、API計費、監控等問題的。
總結
構建復雜的應用本身就是困難的事情。單體應用架構在針對簡單、輕量級的應用的時候是很好的。但是一旦你把那套方案運用在復雜的應用的時候你將會痛苦不堪。盡管微服務架構模式有諸多缺點和實現上的挑戰,但對于復雜的、演進的應用來講是一個更好的選擇。
在后續的文章中我將會詳細介紹微服務的幾個方面,討論一些諸如服務發現、服務部署選擇以及重構單體應用到微服務的的話題。
后記
第一篇文章翻譯完了,說實話膽顫心驚,第一次作大死,厚著臉皮上吧!!!