> 原文出處:http://www.infoq.com/cn/articles/analysis-the-architecture-of-microservice-part-01
作者 :王磊
[TOC=2]
## 概述
多年來,我們一直在技術的浪潮中乘風破浪,揚帆奮進,尋找更優秀的方法來構建IT系統,也一直在積極的學習并觀察先進的公司如何以不同的架構方式構建或者優化其IT系統,來積極應對市場的變化,迅速做出響應,從而為客戶提供更多的價值。
微服務架構模式(Microservice Architect Pattern)是近兩年在軟件架構模式領域里出現的一個新名詞。雖然其誕生的時間不長,但其在各種演講、文章、書籍上所出現的頻率已經讓很多人意識到它對軟件領域所帶來的影響。那到底什么是微服務,當我們談論微服務時,它代表著一種什么樣的含義?微服務適合應用在什么場景下,以及它有什么樣的優缺點?微服務和SOA到底有沒有區別?在接下來的幾部分里,我將為大家揭開微服務的神秘面紗。
不過,在我們開始探討微服務架構之前,讓我們先回顧一下三層應用架構的發展歷程并認識一下什么是單塊架構應用。
## 三層應用架構的發展
對于任何一個軟件應用系統而言,其構建目標都是為了滿足某類用戶的需求,即為用戶傳遞價值。一直以來,軟件的架構設計是決定應用系統是否能夠被正確、有效實現的關鍵要素之一。架構設計描述了在應用系統的內部,如何根據業務、技術、組織,以及靈活性、可擴展性、可維護性等多種因素,將應用系統劃分成不同的部分,并使這些部分彼此之間相互分工、相互協作,從而為用戶提供某種特定價值的方式。
## 應用的三層架構
現實生活中,“層”這個字的含義,對大家一點都不陌生。我們經常說樓房高多少層,蛋糕有幾層等。通常來說,層有好幾種定義,但其中最耳熟能詳的,莫過于“層”能幫助我們劃分出構成某整體事物的,上下相互支撐的的不同部分。譬如說,我們喜歡吃的蛋糕,一般是由三層組成:第一層的蛋糕體、第二層的奶油,和第三層的水果。從頂部至底部,每一層依賴于下一層,從底部到頂部,每一層又支撐著上一層。
在軟件架構模式的領域,經過多年的發展,也有了層的概念:
* 層能夠被單獨構造;
* 層具有區別于其他層的顯著特點;
* 層與層之間能夠互相連接、互相支撐、互相作用,相互協作構成一個整體。
* 層的內部,可以被替換成其他可工作的部分,但對整體的影響不大。
以WEB應用程序為例,在WEB應用程序開發的早期,由于受到面向過程的思維及設計方式的影響,所有的邏輯代碼并沒有明顯的區分,因此代碼之間的調用相互交錯,錯綜復雜。譬如,我們早期使用的ASP、JSP以及PHP,都是將所有的頁面邏輯、業務邏輯以及數據庫訪問邏輯放在一起,這是我們通常提到的一層架構。

隨著JAVA,.NET等高級語言的快速發展,這些語言為開發者提供了越來越方便的的數據訪問機制,如Java語言的JDBC、IO流,或者.NET的ADO.NET等。這時候,數據訪問部分的代碼逐漸有了清晰的結構,但表示邏輯和業務邏輯依然交織在一起,我們稱這個階段為二層架構階段。

隨著面向對象分析、面向對象設計、面向對象原則、設計模式、企業架構模式等理念以及方法論的不斷發展,從為用戶提供功能、以及有效組織軟件結構的角度考慮,WEB 應用中不同職責的部分逐漸被定義在了不同的層次,每一層負責的部分更趨向于具體化,細致化,于是軟件的三層架構逐漸出現了。三層架構通常包括表示層、業務邏輯層以及數據訪問層。

* 表示層
表示層部分通常指當用戶使用應用程序時,看見的、聽見的、輸入的或者交互的部分。譬如,有可能是信息的顯示,音樂的的播放或者可以輸入的文本框,單選按鈕以及可點擊的按鈕等。通過這些元素,用戶同軟件進行交互并獲取期望的價值。目前的用戶接口大部分情況下為WEB方式,當然也可以是桌面軟件的形式,例如. NET的WINFORM或者Java的SWING。
* 業務邏輯層
業務邏輯部分是根據用戶輸入的信息,進行邏輯計算或者業務處理的部分。業務邏輯層則主要聚焦應用程序對業務問題的邏輯處理,以及業務流程的操作,它是大部分軟件系統區別與其他系統的核心。譬如,當用戶點擊一個按鈕后,它可能會觸發業務邏輯部分的代碼進行運算,生成用戶期望的結果。舉例來說,在一個電子商務平臺中,作為用戶,當我們下單購買某個商品后,應用程序的業務邏輯層會對訂單如何進行處理,如何計算折扣、如何配送等進行處理。
* 數據訪問層
在用戶同應用程序交互的過程中,會產生數據。這類數據需要通過某種機制被有效的保存,并在將來能夠被重復使用,或者提供給其他系統。這種機制或者方法就是數據訪問層最關注的部分。也就是說,它關注的是應用程序是如何有效的將數據存儲到數據庫、文件系統或者其他存儲介質中。有一點要注意的是,它關心的是對原始數據的操作(數據庫或者文本文件等存放數據的形式),而非原始數據的存儲介質本身。譬如,在一個電子商務平臺中,商品的信息是如何存儲,圖片的信息是如何獲取的等。
三層架構的出現,一方面是為了解決應用程序中代碼間調用復雜、代碼職責不清的問題。其通過在各層間定義接口,并將接口與實現分離,可以很容易的用不同的實現來替換原有層次的實現,從而有效降低層與層之間的依賴。這種方式不僅有利于幫助團隊理解整個應用架構,降低后期維護成本,同時也有利于制定整個應用程序架構的標準。
另一方面,三層結構的出現從某種程度上也解決了企業內部如何有效的根據技能調配人員,提高生產效率的問題。在大環境下,有效的分層能使不同職責的人員各司其職,更聚焦與個人專業技能的發展和培養。三層結構的出現不僅標準化了復雜系統的邏輯劃分,更幫助企業解決了如何有效形成技術人員組織結構的問題,因此在很長的一段時間里,它一直是軟件架構的經典模式之一。
## 非三層架構
有些人認為,對于一個WEB應用程序,其被自動地分成了三層架構,因為它有三個分離的部件,如圖所示:

這三個部分看起來雖然滿足“層”的概念,但它并不是我們所說的軟件架構的層。就像我們所說的奶油蛋糕,我們可以在蛋糕的底部加上穩固的底座,在蛋糕的外部加上漂亮的包裝盒,但仔細想想,底座可以用不同品牌的底座,可以用紙質材料的,也可以用樹脂材料的;包裝盒可以用紅色的一次性紙袋,也可以用藍色的帶著花紋的鐵盒,它們并不是我們蛋糕組成的必須一部分。
瀏覽器可以獨立存在與WEB應用程序之外,WEB應用程序也可以被不同的瀏覽器訪問,因此瀏覽器不是WEB應用程序的部分。雖然最近幾年,在瀏覽器端,我們可以使用很多JavaScript庫或者框架獨立開發前端應用,但它的范疇超出了我們目前討論的三層架構,更多的屬于富客戶端以及前后端分離的應用。
類似的,數據庫服務器也可以獨立存在于應用程序之外,因此它也不是應用程序的一部分。雖然有些應用程序的邏輯代碼,被設計成必須是在數據庫中運行,例如存儲過程或者觸發器,但這種用法并不推薦,原因是將業務邏輯放在了數據庫本身,大大增加了后期維護的復雜度和數據遷移的成本。
## 單塊架構應用
### 什么是單塊架構應用
雖然軟件的三層架構幫助我們將應用在邏輯上分成了三層,但它并不是物理上的分層。這也就意味著,即便我們將應用架構分成了 所謂的三層,經過開發團隊對不同層的代碼實現,經歷過編譯(如果非靜態語言,可以跳過編譯階段)、打包、部署后,不考慮負載均衡以及水平擴展的情況,最終還是運行在同一個機器的同一個進程中。對于這種功能集中、代碼和數據中心化、一個發布包、部署后運行在同一進程的應用程序,我們通常稱之為單塊架構應用。典型的單塊架構應用,莫過于傳統的J2EE項目所構建的產品或者項目,它們存在的形態一般是WAR包或者EAR包。當部署這類應用時,通常是將整個一塊都作為一個整體,部署在同一個WEB容器,如Tomcat或者Jetty中。當這類應用運行起來后,所有的功能也都運行在同一個進程中。

類似的,基于Ruby On Rails的單塊架構應用,一般邏輯上分為控制器層、模型層以及視圖層,同時代碼存放在遵循一定層級結構 的目錄中。當部署這類應用的時候,通常是使用SSH或者其他一些工具,如[Capistrano](https://github.com/capistrano/capistrano)將整個目錄部署在[Passenger](https://www.phusionpassenger.com/)或者其他WEB容器中。當這類應用運行起來后,所有的功能也都運行在同一個進程中。
因此,對于單塊架構應用的定義,其實是基于分層軟件架構設計的系統基礎之上,從部署模式、運行模式角度去考慮的一種定義方式。
## 單塊架構應用的優勢

* 易于開發
對單塊架構的應用程序而言,開發方式相對較簡單。首先從概念上,現有的大部分工具、應用服務器、框架都是這類單塊架構應用程序,容易理解而且為人所熟知。如果從實踐角度出發,現有的集成開發工具比較適合單塊架構的應用程序,像NetBeans、Eclipse、IDEA等,它們都能夠有效加載并配置整個應用程序的依賴,方便開發人員開發、運行、調試等。
* 易于測試
單塊架構應用程序也非常容易被測試,因為所有的功能都運行在一個進程中,啟動集成開發環境或者將發布包部署到某一環境,一旦啟動該進程,就可以立即開始系統測試或者功能測試。
* 易于部署
對單塊架構的應用程序而言,部署也比較容易。實際上,由于所有的功能最終都會打成一個包,因此只需復制該軟件包到服務器相應的位置即可。當然,部署的方式可以有很多種,最簡單的可以使用SCP遠程拷貝到指定的目錄下,當然也可以使用某些自動化的工具來完成。
* 易于水平伸縮
對單塊架構的應用程序而言,水平伸縮也比較容易。實際上,由于所有的功能最終都會打成一個包,且只能運行在一個進程中,因此單塊架構的水平伸縮,更確切的理解其實是克隆,即新建一個服務器節點,配置好該節點的運行環境,復制軟件包到相應的位置,運行改應用程序。當然,必須要確保負載均衡器能采取某種分發策略,有效的將請求分發到新創建的節點。
## 單塊架構面臨的挑戰
隨著最近幾年互聯網行業的迅猛發展,隨著公司或者組織業務的不斷擴張,需求不斷的增加以及用戶量的不斷增加,單塊架構的優勢已逐漸無法適應互聯網時代的快速變化,面臨著越來越多的挑戰。譬如說,一方面,隨著業務的擴大,如何為用戶提供可靠的服務,如何有效處理用戶增多后導致并發請求數增多,導致的響應慢的問題,以及如何有效解決用戶增多后帶來的大數據量的問題等。另外一方面,隨著公司或者組織業務的不斷擴張,需求不斷的增加,越來越多的人加入開發團隊,代碼庫也在急劇膨脹。在這種情況下,單塊架構的可維護性、靈活性在降低,而測試成本、構建成本以及維護成本卻在顯著增加。
### 1.維護成本增加
隨著應用程序的功能越來越多,團隊越來越大,相應的溝通成本、管理成本、人員協調成本必然會顯著增加。譬如說,對于使用Java編寫的中型應用而言,當代碼量為幾萬行時,可能只需要幾人左右的團隊維護。當代碼量上升到幾十萬行級別時,可能需要幾十人甚至是上百人的團隊。
另外,隨著應用程序功能的增多,當出現缺陷時,有可能引起缺陷的原因組合就會比較多,這也會導致分析缺陷、定位缺陷、修復缺陷的成本相應增高,也就意味著缺陷的平均修復周期可能會花費更長時間。
另外,隨著代碼量的增大,在開發人員對全局功能缺乏深度理解的情況下,修復一個缺陷,還有可能引入其他的缺陷,在自動化測試機制不完善的情況下,很可能導致該過程陷入“修復越多,缺陷越多”的惡性循環。

### 2.持續交付周期長
隨著應用程序的功能越來越多,代碼越來越復雜,構建和部署時間也會相應的增長。在現有部署流水線穩定工作的情況下,對單塊架構應用程序做任何細微的修改以及代碼提交,都會觸發部署流水線,對整個應用程序進行代碼編譯、運行單元測試、代碼檢查、構建并生成部署包、驗證功能等,這也就意味著流水線的反饋周期變長,單位時間內構建的效率變低了。
另一方面,團隊人員的增多,部署流水線運行的時間增加,開發人員能夠提交代碼的時間窗口就相應減少,(因為流水線運行的過程中,是禁止提交代碼的),可能出現長時間等待代碼提交,卻無法提交的情況,極大破壞了團隊的靈活性并降低了團隊工作效率。幾年前,我曾經工作在一個50萬代碼行的單塊架構應用上,整個應用由一個50人左右的分布式團隊負責。通常情況下,從開發人員提交代碼到運行單元測試、構建發布包、運行功能測試、標記為可發布狀態大概需要40分鐘,時間稍微有點長,但團還能忍受。關鍵的問題是開發人員通常都是集中在下午3點左右,完成一定功能的情況下提交代碼,結果就導致3點至5點那個時間段,成了代碼提交的瓶頸,極大影響了該應用的持續集成和構建的效率。
### 3.新人培養周期長
隨著應用程序的功能越來越多,代碼變得越來越復雜的同時,對于新加入團隊的成員而言,了解行業背景、熟悉應用程序業務、配置本地開發環境,這些看似簡單的任務,將會花費更長的時間。我曾經有個朋友,在加入一家世界500強的知名IT公司后,被安排到了一個百萬級代碼的產品組里。他花了將近1個月的時間來熟悉產品文檔、配置開發環境后,才在本地成功的運行起了這個應用。在他從事這份新工作的頭一個月里,我們好幾次聊到他的新工作,得到的答案都是一樣,“看文檔,裝環境”。對個人而言,花一個月時間來配置本地開發環境,其中的滋味和感受大家可想而知,我估計人世間比這更痛苦的事情也沒幾件了。而對公司或者部門而言,本期望員工花費數天就能配置好的環境,卻花了一個月才能完成,這更是極大的浪費。更有甚者,在第一次配置完開發環境后,好幾年都不愿意再升級或者重裝系統,真是一招被蛇咬,十年怕井繩。

### 4.技術選型成本高
傳統的單塊架構系統傾向采用統一的技術平臺或方案來解決所有問題。通常,技術棧的決策是在團隊開發之前經過架構師、技術經理慎重評估后選定的,每個團隊成員都必須使用相同的開發語言、持久化存儲及消息系統,而且要使用類似的工具。隨著應用程序的復雜性逐漸增加以及功能越來越多,如果團隊希望嘗試引入新的框架、技術,或者對現有技術棧升級,通常會面臨不小的風險。
另一方面,互聯網行業不僅市場變化快,而且技術變化也快。譬如,短短幾年幾年時間,光前端JavaScript的框架,就出現了好幾十個,從早一點的[Backbone](http://backbonejs.org/)、[Ember](http://emberjs.com/)到[AngularJS](https://angularjs.org/)、[Ractive](http://www.ractivejs.org/)等等。類似的,后端的框架、工具等也是層出不窮,有興趣的朋友可以參考下[ThoughtWorks的技術雷達](http://www.thoughtworks.com/radar)(該技術雷達是ThoughtWorks對業界技術、工具、語言等發展趨勢的分析以及預測報告)。因此,對單塊架構的應用而言,初始的技術選型嚴重限制了其將來采用不同語言或框架的能力。如果想嘗試新的編程語言或者框架,沒有完備的功能測試集,很難平滑的完成替換,而且系統規模越大,風險越高。
### 5.可伸縮性差
如果應用程序的所有功能代碼都運行在同一個服務器上,將會導致應用程序的擴展非常困難。如果迫切的需要擴展,那么垂直擴展可能是最容易的(錢不是問題)。在大多數情況下,如果舍得砸錢上IBM的服務器、Oracle的數據庫或者來自EMC的存儲設備,不用改變一行代碼,整個世界都變好了。不幸的是,伴隨著業務的增長,數據的增長,垂直擴展會變得越來越吃力,成本越來越高。這也是為什么在業很多公司開始嘗試使用開源,放棄這些昂貴的IOE產品的原因。這下明白為什么近幾年去IOE的呼聲越來越高了吧。
當考慮水平擴展時,通常的做法是建立一個集群,通過在集群中不斷的添加新節點,然后借助前端的負載均衡器,將用戶的請求按照某種算法,譬如輪轉法、散列法或者最小連接法等合理的將請求分配到不同的節點上。但是,由于所有程序代碼都運行在服務器上的同一個進程中,會導致應用程序的水平擴展成本非常高。譬如說,如果應用程序某部分的功能是內存密集型的,如需要緩存大量數據,而另外一部分功能是CPU密集型的,如需要進行大量的運算,那么每次實施水平擴展,運行該應用的服務器都必須有足夠的內存和強勁的CPU來滿足需求。因此,鑒于每個服務器都要提供該應用系統所需要的各種資源,基礎設施的整體花費可能會非常高。當然,如果某些節點保持狀態,如用戶登陸后的會話信息等,更增加了水平擴展的難度。
### 6.構建全功能團隊難

最后,非常微妙的是,隨著應用程序的功能越來越多,代碼變得越來越復雜,其應用程序的復雜結構也會逐漸映射到研發團隊的結構上。康威定律指出:一個組織的設計成果,其結構往往對應于這個組織中的溝通結構。單塊架構的開發模式在分工時往往以技能為單位,比如UX團隊、服務端團隊和數據庫團隊,這樣的分工可能會導致任何功能上的改變都需要跨團隊溝通和協調。譬如說,用戶體驗工程師(UX)更專注負責用戶接口部分,業務層開發者則負責建立服務器后端的業務邏輯,數據庫工程師和DBA們更關注數據訪問組件和數據庫。鑒于這些問題,隨著時間的推移,不僅代碼越來越難以管理,其對團隊結構的影響也越來越明顯。
綜上所述,隨著業務的不斷擴大,需求功能的持續增加,單塊架構已經很難滿足業務快速變化的需要。一方面,代碼的可維護性、擴展性、靈活性在降低;而另一方面,系統的測試成本、構建成本以及維護成本卻在顯著增加。因此,隨著項目或者產品規模的不斷擴大,單塊架構應用的改造與重構勢在必行。
## 總結
互聯網時代的產品通常有幾類特點:創新成本低、需求變化快,用戶群體龐大,它和幾年前我們熟悉的單塊架構應用有著本質的不同。隨著市場變化快、用戶需求變化快、用戶訪問量增加的同時,單塊架構應用的維護成本、人員的培養成本、缺陷修復成本、技術架構演進的成本、系統擴展成本等都在增加,因此單塊架構曾經的優勢已逐漸無法適應互聯網時代的快速變化,面臨著越來越多的挑戰。
## 作者簡介
**王磊**,ThoughtWorks公司首席咨詢師。開源軟件的愛好者和貢獻者,社區活動的參與者,Practical RubyGems的譯者, GDCR西安的組織者。于2012年加入ThoughtWorks,為國內外諸多客戶提供項目交付和咨詢服務;在加入ThoughtWorks之前,曾就職過多家知名外企,具有豐富的敏捷項目實戰經驗。目前致力于微服務架構、虛擬化容器、持續交付、以及Devops的研究與實踐。