漫谈单体架构与微服务架构(上):单体架构
最近微服務架構特別火爆,就跟人工智能、區塊鏈一樣,軟件架構設計如果不提微服務,感覺就像是與世界先進的架構風格和開發技術脫了節似的,各方各面都無法彰顯高大上的氣質。
本來再打算使用一套系列文章來討論微服務的方方面面,但仔細考量之后發現,事情并沒那么簡單:首先拋開系列文章爛尾現象不說,單是微服務架構本身,又豈是一套系列文章能夠完全介紹清楚的?我覺得更多還是需要在微服務架構落地過程中,遇到具體問題時,根據項目實際情況進行反思、討論與深挖,進而再進行總結,以免在后續項目的實踐中重復“踩坑”,這樣才能很好地掌握微服務的理念,成功地實踐微服務架構。今天,就先簡單介紹一下我對微服務架構的理解吧。而要討論微服務,首先回顧一下在微服務架構以各種體系結構模式的形式被提出之前,我們是如何設計服務于各行各業的軟件系統的。
單體架構
相信很多讀者朋友在閱讀這篇文章的時候,已經了解過什么是“單體架構”了,甚至有很多項目已經在正確地或者錯誤地使用了微服務,并且不管實踐方式正確與否,大家的“微服務”也都已經在生產環境正常地服務了。然而,我仍然希望能夠在討論“微服務”之前,介紹一下單體架構,畢竟它也是眾多架構形式中的一種,它仍然有著自己的應用場景。
當我們需要設計一套在線課程發布和訂閱系統(以下簡稱“在線課程”系統)時,傳統做法就是采用早已爛熟的邏輯三層架構:用戶界面層、業務邏輯層和數據訪問層,如果遵循領域驅動設計(DDD)的經典分層架構,基本上也就是四層:表現層、應用層、業務層以及基礎結構層。在系統剛剛開始發布上線的時候,用戶量和每日請求量并不是特別大,所以我們可以將來自于各個層的組件全部部署在一臺物理機器上,因此,層(Layers,邏輯層)與層(Tiers,物理層)之間并非是一對一的關系。當然,也有可能會將用戶界面層、業務邏輯層以及數據訪問層分別部署在不同的物理機器上,實現層(Layer)與層(Tier)的一一對應,從而一定程度上減輕單臺物理機器的負載。多年來,這種架構形式被反復地實現、反復地驗證,并且反復地、成功地被應用在很多生產環境中。在絕大多數情況下,大家并不覺得這樣的架構形式有什么問題,只要設計合理,比如通過引入依賴注入框架、面向方面編程等技術,各個層之間可以完全做到解耦,實現熱插拔式的功能升級和替換也不是什么難事。其實,這種架構形式還是有很多優點的:
結構簡單,容易理解:對于開發人員而言,這是非常重要的一點。經典的分層架構已經相對比較成熟,更容易被更多的開發人員所理解和接受,學習成本也相對比較低,對團隊本身的要求也不是特別高。這不僅使得系統的設計和開發都相對比較容易,而且出錯的幾率會相對低一些。用現在時髦的詞語說,就是“坑相對較少”,開發實現都可以“踩在踩坑人的背上前進”
實現數據一致性相對比較容易,通過本地事務或者分布式事務可以方便有效地保證數據一致性
部署簡單方便:比如這里的在線課程系統,可以方便快速地打包成WAR包,部署到Jetty或者Tomcat容器中,也可以是一個部署在IIS中的.NET解決方案。無論哪種,一次部署完成即可運行整個應用程序
持續集成策略的設計相對容易:基本上團隊可以根據項目的實際情況很容易地設計出持續集成方案,很多情況下,整套解決方案會放在同一個代碼庫中,根據持續集成策略,項目的持續交付也不會有太大壓力
總結起來,這種架構大致可以使用下圖表示,各層組件可以通過相互引用進行相互調用,也可以通過IoC/DI實現解耦,進而實現應用程序“一體化”,這也是“單體架構”一詞的由來:
單點失敗與架構擴展
隨著站點知名度的上升,在線用戶數量也日益增多,或許有一天,部署在單臺物理機上的應用程序無法承載較大的網絡流量和計算負荷,于是出現了訪問無響應甚至發生異常,應用程序變得不可用,不僅影響到客戶,而且也對企業造成了一定的損失。出現這種情況時,或許通過增加內存、提升CPU數量,提高機器配置能夠在一定層度上解決問題,然而這不僅會受到機器自身條件的限制,而且還需要關機一段時間以便完成硬件升級,系統瓶頸依然存在,日后宕機的可能性仍不可避免。
此時,你會發現:我們的單臺物理機上部署的應用程序成為了一個失敗點,而這個點一旦失敗,是無法挽救的,當然,重啟大法可以挽救,不過我們先不研究重啟系統或者重裝系統這些挽救措施,我們單從應用系統本身考慮。行業里將這種現象稱為“單點失敗”。
那么,如果將不同的邏輯層部署到不同的物理服務器上,是不是就不存在單點失敗了呢?顯然不是。如果業務服務器或者數據服務器失效,整個系統仍然是不可用的。那有沒有辦法改善呢?當然有。比如可以對業務服務器做多次部署,然后加上負載均衡:
當然還可以對數據庫本身做集群以及主從備份,以提高數據庫的處理能力,并提高容錯率,降低單點失敗的風險。基于這樣的結構,假設其中某個業務層的服務器失效,那么負載均衡器就會將請求轉交給另一個工作正常的服務器,雖然單點壓力加大,也有可能存在幾秒內請求無法響應的“陣痛”,但也不至于導致整個應用系統失敗,程序仍能正常運行。另外還有一種系統擴展策略,就是將整個應用程序打包,然后進行多次部署,結構大致如下:
無論哪種方式,都是屬于應用程序的橫向擴展,通過將應用程序的不同組件部署在多個物理服務器上從而解決單點失敗的問題。在實踐中,要使得已有的單體架構應用程序能夠支持橫向擴展,還是需要進行一些設計和改裝的,主要宗旨就是,被多次部署的組件必須是無狀態的,或者是有狀態,但經過特殊處理的,也就是要保證組件功能的“冪等性”:無論何時,無論哪個節點,只要接收到的請求相同,那么計算結果必定相等。比如,在經典的ASP.NET應用程序中,我們經常會使用Session對象,默認情況下,Session對象是保存在服務器內存中的,這樣的應用程序如果做橫向擴展,兩臺服務器之間是不冪等的:第一個請求過來,通過負載均衡被分配到服務器A處理,此時改變了Session對象,而下一次請求過來,準備讀取Session對象中的值時,該請求很有可能被負載均衡分配到服務器B上執行,結果可想而知:該請求無法讀取Session值,因為所需的Session對象在服務器B上不存在。
解決這樣的問題有三種方法,第一種方法是通過Web.config配置文件,將Session對象指定保存到SQL Server數據庫,由于數據庫同步機制,Session對象亦可被另一個服務器讀取訪問,于是,也就保證了即使存在負載均衡,客戶端請求仍然可以得到所需的Session對象值。這種方法還是有一定弊端的:除非兩臺應用服務器都連接同一臺數據庫服務器,否則數據庫之間的同步還是會存在一定的延遲,客戶端請求仍然有可能得不到所需的Session值。
第二種方法是在保存Session值的服務器返回執行結果的時候,在返回對象上做一次標記,而在后續的客戶端請求上都帶上這個標記,同時配置負載均衡策略,使得當有相應標記的請求進來時,保證它永遠都只會被指派到對應的服務器上執行,這樣也就確保了客戶端請求能夠得到Session的數據。這種做法也有弊端,它干預了負載均衡策略,造成負載均衡失效。
第三種方法就比較讓人不舒服了,那就是禁用Session機制,以其它方案代替。在這里我很難說清楚“其它方案”是什么,還是得根據實際情況進行選擇,一句話:it depends。
我曾經成功地使用ASP.NET+IIS+Windows NT Network Load Balancer實現了應用程序的橫向擴展,總體來說效果還是不錯的,前提就是遵循微軟推薦的最佳操作(follow the best practices recommended by Microsoft),而這些最佳操作當中,就有我這里討論的Session問題。或許你會覺得我還在炒冷飯,花這么些篇幅來介紹一些過時幾百年的技術,感覺并沒有什么價值。其實,單體架構橫向擴展的經驗,同樣也適用于微服務架構,因為我們需要避免單點失敗。
說起應用程序擴展,這里我們提到了通過增加內存、CPU等硬件資源來提高系統吞吐量和處理能力的縱向擴展,也提到了將一個或多個組件甚至是整個應用程序冪等地部署到多臺服務器上的橫向擴展。但即使是這兩種不同的擴展方式,在實際項目中具體選擇哪種,還是有一定講究的,詳細可以參考《橫向擴展與縱向擴展的對比與選擇》這篇文章(抱歉是E文的)。
單體架構的云端部署
在云環境中,應用程序的縱向擴展是非常容易的,只需要修改虛擬機的配置即可。其實在云上有很多種玩法,光是修改虛擬機配置就不一定、甚至通常情況下也不會通過人工的方式完成。云托管虛擬機都有監控和自動伸縮的能力,可以根據設置的策略實現縱向動態擴展。
應用程序橫向擴展也是非常容易的,比如可以使用自動可伸縮集(Auto-scale Set)來實現。首先通過監控服務來獲取單臺虛擬機的健壯性,如果存在響應時間延長或者超時,自動可伸縮集會根據已經設置的策略,動態部署一臺或多臺新的虛擬機,同時修改負載均衡器的配置,將新增的機器加入負載均衡,只要配置得當,所有的事情是無需人工干預的。其實在云端,重啟大法和重裝大法都是非常常用的方式,重啟機器或者重新安裝一臺新的機器,成本要比調試應用程序所需要的時間、人力低太多。
這里再多聊幾句有關云環境下應用程序的實現問題,應該盡量選擇云供應商托管的服務,而不是在云中創建虛擬機并讓自己的應用程序運行在虛擬機中。選用托管服務不僅方便快捷安全,而且能夠做到高可用性,一旦出現故障,可以直接聯系云供應商輔助解決。然而如果選擇虛擬機的話,部署和維護都要自己處理,還需要自己設計自動伸縮和負載均衡策略,如果出現問題,也只能自己解決,云供應商無法進入虛擬機內部并提供幫助。
總的來說,無論是部署在本地還是部署在云端,要想獲得良好的擴展性,都需要遵循一定的設計模式,否則容易導致數據不一致、系統穩定性差等嚴重問題。
單體架構的弱勢
單體架構最主要的優勢就是結構簡單容易理解,所應用的技術和實踐方案都非常成熟。在應用程序規模相對比較小的時候,單體架構還是非常合適的,但隨著應用程序體積日趨龐大,慢慢地也就突顯出了一些弱勢。
龐大的代碼體系使得代碼庫也變得龐大,團隊合作變得越來越復雜,比如代碼沖突發生的可能性會大大增加,解決代碼沖突的成本也隨之增大
龐大的代碼體系會使得代碼編輯工具和IDE變得不堪重負
系統構建和系統部署的時間越來越長,構建一次需要花幾個小時甚至一天的時間
所有業務邏輯都實現在同一層中,無法根據業務劃分進行更細粒度的擴展。基于業務的擴展需求依然來自于實際場景,比如在線課程系統中,通常情況下查詢和瀏覽的訪問量會要大于注冊和下訂單的流量,于是,我們應該可以根據實際情況,適當增加查詢和瀏覽相關模塊的部署。而單體架構卻要求整個業務層統一部署,會對硬件資源造成一定的浪費:如果查詢和下訂單能夠獨立部署,那么我可以使用兩臺機器來運行查詢服務,并能剛好滿足需求;但如果是放在一起統一部署,那么兩臺機器就未必能夠達到查詢所需的系統吞吐量,因為還有另一部分流量需要分給訂單系統,此時,可能就需要部署三臺機器
單體架構在高可用性方面也有一定的缺陷,比如,倘若數據訪問層出現問題,那么系統中的所有業務都無法正常完成,應用程序站點也就完全失效了。但如果可以將不同的業務領域拆分開來獨立運行,或許其中一個部分無法正常工作,但其它的業務邏輯仍然可以正常工作,應用程序站點也不至于完全無法訪問。比如,在線課程系統中,用戶賬戶管理服務如果出現問題,充其量也就是新用戶無法正常注冊,老用戶暫時無法登錄,但站點還是能夠正常運行,訪客仍然可以查詢課程
整個系統的技術選型從一開始就已經定好,并且在開發的過程中很難變更,即使市面上出現了更好的解決方案,也無法輕易地將新的更優秀的技術方案引入項目,因為成本會非常大。慢慢地,等應用程序功能基本完善后,或許所采用的技術已經不再主流。你或許會覺得這并不是什么嚴重的問題,當然,不是特別嚴重,只是當新生事物出現時,我們或許會失去一些機會,比如,老的系統如何接入到云端,以獲得自動部署、自動伸縮等優勢。到最后,或許只能用新的技術重寫整個系統,但這樣的循環永遠終結不了
根據不同業務場景選擇不同的基礎結構服務變得相對比較困難。比如,在線課程查詢服務可以基于Elastic Search進行快速查詢,然而用戶信息的創建卻又無需使用Elastic Search組件,或許使用關系型數據庫會更合適。將兩套數據存儲機制都引入應用程序,又使得應用程序本身變得更為臃腫
在項目的開發過程中,我們或許還可以總結出有關單體架構的更多弱勢,在此也就不一一列舉了。這里列出的幾條中,有不少都跟應用程序本身所立足的業務領域有關,比如希望能夠根據業務來決定系統的伸縮策略,根據業務來決定系統的技術方案等等。這里也就給我們一個啟示:我們是不是可以根據業務將單體架構的應用程序拆開來,讓它們能夠被獨立開發、獨立運維,最后又以某種方式糅合在一起呢?當然有辦法,這就是接下來我想談談的微服務架構。
微服務架構
限于篇幅,就不在這篇文章中說微服務架構的事情了,因為肯定是說不完的。微服務架構可以說是針對單體架構的弱勢提供了完美的解決方案,然而,這并不是說單體架構就一無是處,大家可以不管三七二十一直奔微服務架構了。單體架構的魅力就在于它的簡單,如果你的應用程序沒有上面描述的這些單體架構所做不到的需求,那么,或許繼續使用單體架構會更合適,也會讓你更舒服。當你讀完我接下來這篇討論微服務架構的文章后,或許你會同意回歸單體架構的,因為微服務架構的成功實踐是比較困難的,不僅需要對架構整體以及獨立的微服務進行細致的考慮和設計,而且還要求團隊有著較高的素質。事實上,已經有一些企業和項目開始從微服務架構轉向單體架構,因為各種因素使得團隊無法看到微服務架構成功實踐的曙光。
無論怎樣,軟件系統架構選擇沒有對錯,只有合適合理,甚至可以說是沒有架構:某些部分使用A技術更合適,某些部分使用B技術更合理。寫到這里,我沒有捧高單體架構而貶低微服務架構的意思,各種架構風格都有它們適用的地方,撰寫此文也只不過是給關注架構設計的讀者做個參考。總之一句話,架構沒有銀彈。
在《為什么Segment會從微服務退回單體架構?》一文中,有以下這段話,在此引用,也算是為下文做個鋪墊吧:
“比這還糟呢。據我觀察多數微服務架構根本就沒考慮一致性(“我們才不要亂七八糟的事務!”),盲目地隨大流還樂在其中。我搞不懂為啥子人們會覺得,把軟件模塊拆分開來然后用緩慢不可靠的網絡和弱爆的手動連接REST處理串起來,就能神奇地讓架構面目一新哩?我覺得人們產生這種生產力幻覺的原因是:”我把這些都搞定啦,現在我也有一套’管它是什么即服務‘的先進玩意兒嘍!看看那酷斃的數據面板上閃爍的小綠燈吧,我們可是為了它干了好幾個月呢!“”
總結
以上是生活随笔為你收集整理的漫谈单体架构与微服务架构(上):单体架构的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一个迄今为止最快的并发键值存储库FAST
- 下一篇: 微软MVP张善友告诉你,微服务选型要注意