當了10年碼農,卻尚未寫過Web後端。最近可能要上Web項目,先從架構入手,趕忙先充充電,以爲一篇文章寫得還不錯,分享一下。html
http://www.infoq.com/articles/microservices-intro前端
本文描述了當前正快速流行的微服務架構。微服務背後的主要思想是將大型的、複雜的、且須要長期演進的應用分解爲一個個相互做用且不斷演進的服務。顧名思義「微服務」這個詞的重點在於服務必須「微」小。git
對於微服務的規模,有些社區建議一個服務應該在10到100邏輯行之間,這不是關鍵,由於咱們作微服務的目的在於想要解決一些實際問題,這些開發、部署相關的問題咱們會在下面討論到。因此有些服務可能很是小型,而有些可能有點大。github
微服務並非什麼很新的理念,相似的理念如分佈式系統和SOA已經出現好久,甚至微服務也能夠叫作輕量級SOA。事實上你能夠把微服務理解爲沒有ESB和WS的SOA。數據庫
使用微服務架構的主要動機是什麼?他和傳統的總體集成架構(monolithic)相比怎樣?微服務架構有什麼優缺點?在微服務架構中,如何解決服務間通訊和分佈式數據管理等關鍵技術問題?儘管微架構不是什麼新穎的想法,但由於它和傳統的SOA不盡相同,仍是值得對其進行探討。固然探討的最終目的是想要解決當下不少Team面臨的不少問題。apache
早期的WEB企業應用大多將全部的服務端組件都放到一塊。對於Java企業應用來講可能就是一個WAR或EAR文件,其餘語言(如Ruby或C++)開發的應用也相似。編程
設想一下咱們要作一個圖1的網店應用,包括訂單、庫存、信用卡和物流等管理功能。後端
這個應用會包含前端和後端。前端包括用戶界面和操做,後端包括商品管理、訂單管理、帳戶管理等服務,全部後端服務會共享商品、訂單、用戶等業務數據設計模式
儘管咱們能夠將應用劃分爲多個功能,但整個應用在部署來說可能就是一個運行在Tomcat上的WAR文件。api
這種所謂的總體集成架構的優點在於:IDE友好,由於IDE擅長管理單個工程;方便啓動測試,由於僅僅須要啓動一個應用就能夠開始測試了;方便部署,只要拷貝一個文件就夠了。
這種架構對於相對較小的應用是沒有問題的,但對於複雜應用就不能適用了。首先一個很大的應用不利於開發人員理解和維護源代碼;另外對於很大的應用要出個一版本須要不少開發人員配合,測試周期也很長,這不利於快速迭代上線。
這種架構還有一個大問題是不利於嘗試並引入新技術。好比你發現一個很好用的新框架,可是除了重寫整個應用外你根本沒法引入這個新框架,而重寫整個應用是風險極高的,基本上不現實。因此總的來講這種架構不適合大型的、長期演進的系統。
幸運的是咱們有其餘能更好支持伸縮的架構。「伸縮藝術」中提到了一種三維伸縮模型:圖2中的伸縮立方
這個模型的X軸方向上的擴展,是運行在負載均衡器背後的多個如出一轍的應用,這是提高服務容量和可靠性的好方法。
Z軸上有點像X軸,一樣運行多個如出一轍的應用,區別在於這些應用不是運行在負載均衡器背後,而是運行在路由背後。一種經常使用的路由是根據服務數據的Primary Key分區路由;另外一種經常使用的路由是根據客戶的等級分體驗等級路由(爲VIP用戶提供更快的響應速度,更大的容量等)。
Y軸也能夠叫作功能分解軸。X軸和Z軸全部應用拷貝都是同樣的功能,而Y軸不是,Y軸上的每一個部件的功能都不同。對應到應用,就是將總體集成架構分解爲一組功能不一樣的服務。每一個服務負責一組相關業務,如訂單管理、客戶管理等。
將一個應用分解爲一組服務有點像藝術創做,不容易說清楚,但有一些經常使用的策略。好比從動詞或User Case的角度分解。仍是在線商店的例子,咱們立刻會看到分解事後的狀況,其中就有一個訂單管理UI服務對應於訂單操做的User Case的UI部分。再好比從名詞或者資源的角度分解。按照這種角度分解出來的服務,會負責對應某個資源的處理。後面咱們也會看到在線商店中的商品管理服務,負責處理全部的商品資源。
理想狀況下,一個服務負責一件事情,或幾件小事情。Bob Martin有一個關於一個類僅負責一件事情理論(single-responsibility principle-SRP)的PDF。SRP將一個類定義爲僅處理一種變化的責任主體,這個理論一樣能夠運用於服務的設計。另外也能夠參考Unix命令,Unix系統提供了大量的命令,好比grep、cat,和find,每個命令能出色得完成單一功能,同時又能和其餘命令組成Shell Script完成複雜的功能。因此也參照Unix命令的思想,按照單一功能思路來建立服務。
須要強調的一點是,分解應用的目的並非爲了獲得精確地「小」(好比10-100邏輯行)服務,而是要發現並解決總體集成架構的問題和不足。一些服務能夠很小,而另外一些也可能比較大。
回到在線商店的例子,按照Y軸分解的話,咱們會獲得圖3的架構。
通過分解後的應用變成了由一些服務組成,這些服務實現了不一樣的前端或後端功能。前端服務包括商品管理的UI(商品查找和瀏覽功能)、訂單管理UI(購物車和訂單提提交功能)等,相應的後端也有服務完成對應的功能。咱們已經把應用的各個模塊都變成了獨立的服務,讓咱們看看這樣會帶來哪些改變。
任何架構都是有優缺點的。
由此,開發人員更容易理解代碼;IDE工具能跑得更歡,變相得提高了開發效率;啓動更加快捷,這樣針對具體某個模塊的測試、Debug工做的效率也獲得提升。
當服務須要調整時,可由負責該服務的開發人員單獨完成發佈和部署,不須要其餘人陪同參與(被動加班等);微服務架構使得持續部署成爲可能(大型工程若是不拆分,又必須按明確的時間節奏提交各個階段的版本,那麼80%的可能都被20%的卡住)。
能夠爲每一個服務單獨進行負載均衡或硬件配置上的伸縮。這在總體集成架構下是作不到的,由於總體集成架構下的每一個模塊對資源有着不一樣的需求(若有些模塊是IO密集型,有些模塊是CPU密集型),而這些模塊是在一個應用程序中部署的。
團隊的存在乎義就在於可以產生1+1>2的合做價值。對於開發團隊而言,拋開主觀因素,其存在乎義主要在於成員之間方便交流。而這種交流的根本驅動在於程序模塊之間須要相互交流。當採用微服務架構後,模塊之間的交流討論需求更少(模塊間接口其實不見得會變少,只是對於測試、Debug、部署、線上問題定位等,就不須要太多交流了),從而能帶來更鬆散和人性化的團隊架構。
好比一個服務的內存泄漏問題只會影響這個服務,其餘沒有內存泄漏問題的服務通常仍是能正常工做,這樣就很容易界定出哪一個服務出了問題。對應的,在像總體集成架構下,一個模塊的內存泄漏可能致使整個應用出問題,這經常使得服務宕機。微服務之間的界限最少應該在進程等級,除非有一些更高檔的沙盒能夠隔離微服務。
理論上來說,當須要開發或重構一個微服務的時候,你能夠選擇任何語言和框架,固然不少組織中這種選擇是有範圍的。不太重點是,開發人員不用爲他以爲很愚蠢的技術決策買單了。另外由於微服務都比較小,因此若是一個技術嘗試失敗了,也不會太影響這個工程的進度。在總體集成架構下,每每一旦決定了採用的語言和框架,後面全部人都必須用這個才行。
這一點對於不少技術人員幹活時的心情的影響,是很是大的。就像你本有機會娶得女神,你爹非要給你按一個如花,那在後面婚姻不如意時,更容易埋怨當初你爹的坑爹決策。而其實過日子老是會遇到不如意,關鍵在因而本身的自由選擇就會心甘情願承擔責任。有自由纔有責任嘛。
開發人員須要實現進程間通訊的機制,這是分佈式系統的必備要素,而微服務必然使用分佈式系統;IDE等開發工具不會爲多個應用交互的分佈式系統作不少支持,它們通常都設計爲開發單個應用。這些都是放棄總體集成架構後帶來的新問題。進程是非業務層面的支撐,能夠理解爲一種沙盒(部署時的執行鏡像隔離和運行時的內存隔離),因此理論上其餘沙盒若是能完成更好的隔離,也能夠代替進程隔離。
增長了不少可獨立部署的服務(Y軸伸縮),而不少服務還有多個實例(X軸和Z軸伸縮),這些都須要在生產環境下管理起來。這種複雜的管理須要高度自動化。這種自動化能夠本身開發實現,也能夠採用PaaS技術,好比Netflix 的Asgard及相關組件搭建一個平臺,或Pivotal Cloud Foundry這樣現成的提供商
多個服務之間存在相互依賴,這種依賴只有開發團隊清楚,因此須要根據服務間依賴肯定服務的部署順序圖。而在總體集成構架下,全部的依賴都是應用在開發階段搞定,無需延伸到部署環節。或者由開發人員完成部署:)
上面提到的大部分微服務架構優點,在應用開發初期都不存在,由於一開始應用老是比較簡單的,不會面臨那些須要微服務架構來解決的問題。而且一開始就採用微服務架構會增長了開發成本(須要不少業務無關的支撐)。因此在開發過程當中是否採用或什麼時候開始切換到微服務架構就是個問題了。
項目一開始都是但願快速實現業務需求,完成開發任務。從Y軸上去分解會使初期的迭代交付更慢。以後,當項目的主要目標慢慢從實現業務變成如何讓業務作到可伸縮時,錯綜複雜的模塊間依賴或許已經不容許你將應用分解爲微服務了。
因而可知,什麼時候採用或切換到微架構是須要慎重考慮的。不過對於那些顯然須要具有很強伸縮能力的應用,好比2C的Web應用或SaaS應用,採用微服務架構通常是不會錯的。大名鼎鼎的eBay(PDF),Amazon.com,Groupon,和Gilt已經都從總體集成架構切換到了微服務架構。
到目前咱們已經討論了微服務架構的優劣,下面看看微服務架構設計過程當中的幾個關鍵問題,先從通訊機制提及。
在微服務架構中,客戶端和應用之間的通訊機制,以及應用內部模塊之間的通訊機制,都和總體集成架構中不同。咱們先看客戶端和微服務之間的通訊,再看應用內部之間的通訊。
在總體集成架構中,瀏覽器(B/S)或客戶端(C/S)發起HTTP請求到負載均衡路由,而後請求被路由到運行着的N個徹底相同的應用中的一個去處理。那麼微服務架構下,這些HTTP請求發給誰呢?
像智能手機App這樣的客戶端,會發送不少分別發送RESTful HTTP請求到各個服務,如圖4。
初看這種模式挺好的,可是顯然沒有考慮到不一樣的客戶端對於API和數據需求的粒度差別。好比,Amazon.com上要顯示一個頁面可能會調用到100多個服務(詳見)。如此龐大的網絡調用,且不說移動客戶端,就是桌面客戶端下也顯得性能低下,用戶體驗可想而知。
更好的方式應該是客戶端在渲染一個頁面時減小服務調用次數,也許是將多個調用合併爲之後,而後發送給一個獨立的API網關服務器。如圖5。
API網關處在客戶端和微服務之間,他既爲移動客戶端提供粗粒度的API,也爲桌面客戶端提供細粒度的API。如上圖中移動客戶端僅須要一個請求便可得到桌面客戶端須要3個請求才能獲取的信息。API網關將粗粒度的API請求分解後,經過高性能的局域網絡向微服務發起請求。好比在Netflix上,一個請求對應微服務的扇出爲6左右(詳情)。
API網關不只能更好地爲低質量網絡下的客戶端服務,也對微服務提供了一層封裝。這使得只要業務接口不變,實現業務的服務能很方便地靈活改變,如拆分、合併等。僅須要API網關稍微修改對粗粒度API的處理便可,而客戶端徹底不受影響。
API網關是於客戶端和微服務之間通訊的關鍵一環,下面咱們看看微服務和微服務之間如何通訊的。
在集成架構下,應用模塊之間的通訊一般直接由編程語言的調用實現。但在微服務架構下,每一個微服務都運行在獨立的進程,必須藉助進程間通訊(IPC)來交互。
REST和SOAP都是基於HTTP的同步調用,這是兩種友好的網絡技術,同時容易實現請求-響應設計模式。HTTP技術的一個侷限是它不支持像發佈-訂閱這樣的設計模式。
另外一個問題是HTTP訪問須要知道服務器地址和端口號,在一些大系統這個問題會凸顯出來。好比雲平臺上部署的服務,這些服務的運行環境可動態伸縮,並且服務自己也是有須要時才實例化,不須要時銷燬,這種狀況下,想要人爲管理各個服務的HTTP地址也是一個複雜的事情。因此微服務架構的應用內部須要一個服務發現機制,好比創建一個服務註冊機制(如Apache ZooKeeper和Netflix Eureka)。一些應用裏甚至規定微服務註冊時必須內置負載均衡器(進而又負載均衡器接管HTTP路由?),例如在Amazon VPC中的ELB。
這裏的同步不是編程語言層面的同步,而是指HTTP請求的響應有一點的時限要求。
好比基於AMQP的Message Broker就是一種異步消息機制。這個機制裏,將消息生產者和消費者分離開來,Message Broker會緩存消費者尚未處理的消息,生產者僅需和Message Broker交互而徹底不用關心消費者,由此也再也不須要服務發現了。同時HTTP不能實現的像發佈-訂閱這種雙向通訊模式,該機制也能實現。不過請求-響應這種模式在消息機制下就不那麼天然了(全部消息都是對等的,不像HTTP裏那樣有自然區分)
缺點這種機制須要一個Message Broker,這無疑使得微服務下複雜的部署更加複雜。
二者各有特色,能夠二者兼用。
爲了讓微服務之間解耦,數據庫的分解是理所固然的,每一個微服務應該有本身專有的數據庫(至少不能共享表結構)。甚至理論上來說每一個微服務能夠根據本身的須要選用不一樣的數據庫實現——這種設計被稱爲多樣持久化(polyglot-persistence)。所以,應用被分解爲一個個微服務,對應的數據庫也跟着被分解了。
好比,一個微服務若是須要ACID交互,那麼能夠用關係型數據庫;而另一個微服務若是須要管理社交網絡的話,反而會選擇圖數據庫。
暫且先不討論數據庫分解如何重要,數據庫分解帶來了一個新的問題:如何處理那些須要多個服務和多個數據庫的請求。咱們先來看對數據的讀請求,再看寫。
在在線應用商店的例子中,假設每一個用戶有一個信用額度(全部未付款訂單交易金額總和不能超過信用額度)。那麼當用戶提交一個訂單時,系統必須檢查全部未付款訂單的總額是否超過了信用額度。在微服務架構下,這個用例中涉及到用戶服務和訂單服務兩個微服務,其中訂單服務須要向用戶服務查詢信用額度信息。
一種方法是訂單服務每次須要信用額度信息時向用戶服務查詢,這種實現最簡單。缺點是訂單服務的可用性依賴於了用戶服務,另外每次都作跨進成查詢比較耗時。
另外一種方法是訂單服務中緩存一個信用額度信息。這樣訂單服務對用戶服務的依賴就弱不少,並且也不會產生額外的查詢耗時。缺點是必須引入另外的機制保證這個緩存的信用額度能獲得及時更新。
相似像保證訂單服務和用戶服務中的信用額度一致性的問題,在涉及到多服務寫請求處理的時候,是很廣泛的。
一個方案是利用數據庫的分佈式事務,在用戶服務處理信用額度更新時,除了更新本身維護的數據外,提交一個分佈式事務到訂單服務的數據庫。這種機制能保證兩邊的信用額度始終相同。缺點是下降了系統可用性,由於必須處理分佈式事務的服務或數據庫均可用,這個更新才能完成,並且分佈式事務不支持自恢復,也不被新不少技術框架支持(REST,NoSQL等)。
另外一種方法是異步事件驅動回覆。也就是若是一個服務關心一個數據更新,那麼就對這個數據更新註冊一個監聽,當數據發生更新時,由負責更新的服務發出一個更新事件,以前註冊的監聽者就都能收到這個更新通知了。在這裏,用戶服務更新了信用額度後,發佈一個CustomerCreditLimitUpdatedEvent,裏面有用戶ID和新的信用額度兩個參數。而訂單服務註冊過這個事件因此能夠收到新的信用額度。這個流程圖6。
這個方法主要的優勢是將更新數據的生產者和接受數據更新的消費者解耦,這樣兩個微服務相互不依賴,可獨立運行。即便訂單服務在信用額度更新時沒有運行,也能夠在運行後收到最新的信用額度。其實這是用數據一致性換取了服務可用性(事件發送和處理之間有時間差),這就致使系統必須設計爲容忍必定的數據不一致,對應到程序開發時就須要增長另外的機制來檢查並糾正這種數據不一致。儘管如此,這種方法是如今不少程序採用的。
總體集成架構在企業應用中很常見。這種架構下的小應用,不管是開發、測試仍是部署,都能較好完成。可是對於複雜的大型程序,總體集成架構則成爲了開發和部署的絆腳石。繼續發佈基本已經不可能了,開發也被緊緊限制在以前選擇的技術框架中。所以對於大型應用,採用微服務架構將其分解爲一組服務值得一試。
微服務架構優勢多多。一個微服務的源代碼很容易被理解,開發部署也不須要對其餘模塊有什麼依賴。另外在一個微服務中應用新技術框架更簡單容易。
微服務架構缺點也不是沒有。東西一分解之後,一個應用會變成不少零碎,你或許須要一個像PaaS那樣高度自動化的平臺來管理這些零碎。在開發階段,你還要考慮如何處理數據碎片化。總的來講,對於須要快速迭代的大型程序,特別是SaaS風格的應用來說,值得一試。