1. 程式人生 > >漫談單體架構與微服務架構(上):單體架構

漫談單體架構與微服務架構(上):單體架構

最近微服務架構特別火爆,就跟人工智慧、區塊鏈一樣,軟體架構設計如果不提微服務,感覺就像是與世界先進的架構風格和開發技術脫了節似的,各方各面都無法彰顯高大上的氣質。

640?wx_fmt=jpeg

本來再打算使用一套系列文章來討論微服務的方方面面,但仔細考量之後發現,事情並沒那麼簡單:首先拋開系列文章爛尾現象不說,單是微服務架構本身,又豈是一套系列文章能夠完全介紹清楚的?我覺得更多還是需要在微服務架構落地過程中,遇到具體問題時,根據專案實際情況進行反思、討論與深挖,進而再進行總結,以免在後續專案的實踐中重複“踩坑”,這樣才能很好地掌握微服務的理念,成功地實踐微服務架構。今天,就先簡單介紹一下我對微服務架構的理解吧。而要討論微服務,首先回顧一下在微服務架構以各種體系結構模式的形式被提出之前,我們是如何設計服務於各行各業的軟體系統的。

單體架構

相信很多讀者朋友在閱讀這篇文章的時候,已經瞭解過什麼是“單體架構”了,甚至有很多專案已經在正確地或者錯誤地使用了微服務,並且不管實踐方式正確與否,大家的“微服務”也都已經在生產環境正常地服務了。然而,我仍然希望能夠在討論“微服務”之前,介紹一下單體架構,畢竟它也是眾多架構形式中的一種,它仍然有著自己的應用場景。

當我們需要設計一套線上課程釋出和訂閱系統(以下簡稱“線上課程”系統)時,傳統做法就是採用早已爛熟的邏輯三層架構:使用者介面層、業務邏輯層和資料訪問層,如果遵循領域驅動設計(DDD)的經典分層架構,基本上也就是四層:表現層、應用層、業務層以及基礎結構層。在系統剛剛開始釋出上線的時候,使用者量和每日請求量並不是特別大,所以我們可以將來自於各個層的元件全部部署在一臺物理機器上,因此,層(Layers,邏輯層)與層(Tiers,物理層)之間並非是一對一的關係。當然,也有可能會將使用者介面層、業務邏輯層以及資料訪問層分別部署在不同的物理機器上,實現層(Layer)與層(Tier)的一一對應,從而一定程度上減輕單臺物理機器的負載。多年來,這種架構形式被反覆地實現、反覆地驗證,並且反覆地、成功地被應用在很多生產環境中。在絕大多數

情況下,大家並不覺得這樣的架構形式有什麼問題,只要設計合理,比如通過引入依賴注入框架、面向方面程式設計等技術,各個層之間可以完全做到解耦,實現熱插拔式的功能升級和替換也不是什麼難事。其實,這種架構形式還是有很多優點的:

  1. 結構簡單,容易理解:對於開發人員而言,這是非常重要的一點。經典的分層架構已經相對比較成熟,更容易被更多的開發人員所理解和接受,學習成本也相對比較低,對團隊本身的要求也不是特別高。這不僅使得系統的設計和開發都相對比較容易,而且出錯的機率會相對低一些。用現在時髦的詞語說,就是“坑相對較少”,開發實現都可以“踩在踩坑人的背上前進”

  2. 實現資料一致性相對比較容易,通過本地事務或者分散式事務可以方便有效地保證資料一致性

  3. 部署簡單方便:比如這裡的線上課程系統,可以方便快速地打包成WAR包,部署到Jetty或者Tomcat容器中,也可以是一個部署在IIS中的.NET解決方案。無論哪種,一次部署完成即可執行整個應用程式

  4. 持續整合策略的設計相對容易:基本上團隊可以根據專案的實際情況很容易地設計出持續整合方案,很多情況下,整套解決方案會放在同一個程式碼庫中,根據持續整合策略,專案的持續交付也不會有太大壓力

總結起來,這種架構大致可以使用下圖表示,各層元件可以通過相互引用進行相互呼叫,也可以通過IoC/DI實現解耦,進而實現應用程式“一體化”,這也是“單體架構”一詞的由來:

640?wx_fmt=png

單點失敗與架構擴充套件

隨著站點知名度的上升,線上使用者數量也日益增多,或許有一天,部署在單臺物理機上的應用程式無法承載較大的網路流量和計算負荷,於是出現了訪問無響應甚至發生異常,應用程式變得不可用,不僅影響到客戶,而且也對企業造成了一定的損失。出現這種情況時,或許通過增加記憶體、提升CPU數量,提高機器配置能夠在一定層度上解決問題,然而這不僅會受到機器自身條件的限制,而且還需要關機一段時間以便完成硬體升級,系統瓶頸依然存在,日後宕機的可能性仍不可避免。

此時,你會發現:我們的單臺物理機上部署的應用程式成為了一個失敗點,而這個點一旦失敗,是無法挽救的,當然,重啟大法可以挽救,不過我們先不研究重啟系統或者重灌系統這些挽救措施,我們單從應用系統本身考慮。行業裡將這種現象稱為“單點失敗”。

那麼,如果將不同的邏輯層部署到不同的物理伺服器上,是不是就不存在單點失敗了呢?顯然不是。如果業務伺服器或者資料伺服器失效,整個系統仍然是不可用的。那有沒有辦法改善呢?當然有。比如可以對業務伺服器做多次部署,然後加上負載均衡:

640?wx_fmt=png

當然還可以對資料庫本身做叢集以及主從備份,以提高資料庫的處理能力,並提高容錯率,降低單點失敗的風險。基於這樣的結構,假設其中某個業務層的伺服器失效,那麼負載均衡器就會將請求轉交給另一個工作正常的伺服器,雖然單點壓力加大,也有可能存在幾秒內請求無法響應的“陣痛”,但也不至於導致整個應用系統失敗,程式仍能正常執行。另外還有一種系統擴充套件策略,就是將整個應用程式打包,然後進行多次部署,結構大致如下:

640?wx_fmt=png

無論哪種方式,都是屬於應用程式的橫向擴充套件,通過將應用程式的不同元件部署在多個物理伺服器上從而解決單點失敗的問題。在實踐中,要使得已有的單體架構應用程式能夠支援橫向擴充套件,還是需要進行一些設計和改裝的,主要宗旨就是,被多次部署的元件必須是無狀態的,或者是有狀態,但經過特殊處理的,也就是要保證元件功能的“冪等性”:無論何時,無論哪個節點,只要接收到的請求相同,那麼計算結果必定相等。比如,在經典的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)來實現。首先通過監控服務來獲取單臺虛擬機器的健壯性,如果存在響應時間延長或者超時,自動可伸縮集會根據已經設定的策略,動態部署一臺或多臺新的虛擬機器,同時修改負載均衡器的配置,將新增的機器加入負載均衡,只要配置得當,所有的事情是無需人工干預的。其實在雲端,重啟大法和重灌大法都是非常常用的方式,重啟機器或者重新安裝一臺新的機器,成本要比除錯應用程式所需要的時間、人力低太多。

這裡再多聊幾句有關雲環境下應用程式的實現問題,應該儘量選擇雲供應商託管的服務,而不是在雲中建立虛擬機器並讓自己的應用程式執行在虛擬機器中。選用託管服務不僅方便快捷安全,而且能夠做到高可用性,一旦出現故障,可以直接聯絡雲供應商輔助解決。然而如果選擇虛擬機器的話,部署和維護都要自己處理,還需要自己設計自動伸縮和負載均衡策略,如果出現問題,也只能自己解決,雲供應商無法進入虛擬機器內部並提供幫助。

總的來說,無論是部署在本地還是部署在雲端,要想獲得良好的擴充套件性,都需要遵循一定的設計模式,否則容易導致資料不一致、系統穩定性差等嚴重問題。

單體架構的弱勢

單體架構最主要的優勢就是結構簡單容易理解,所應用的技術和實踐方案都非常成熟。在應用程式規模相對比較小的時候,單體架構還是非常合適的,但隨著應用程式體積日趨龐大,慢慢地也就突顯出了一些弱勢。

  1. 龐大的程式碼體系使得程式碼庫也變得龐大,團隊合作變得越來越複雜,比如程式碼衝突發生的可能性會大大增加,解決程式碼衝突的成本也隨之增大

  2. 龐大的程式碼體系會使得程式碼編輯工具和IDE變得不堪重負

  3. 系統構建和系統部署的時間越來越長,構建一次需要花幾個小時甚至一天的時間

  4. 所有業務邏輯都實現在同一層中,無法根據業務劃分進行更細粒度的擴充套件。基於業務的擴充套件需求依然來自於實際場景,比如線上課程系統中,通常情況下查詢和瀏覽的訪問量會要大於註冊和下訂單的流量,於是,我們應該可以根據實際情況,適當增加查詢和瀏覽相關模組的部署。而單體架構卻要求整個業務層統一部署,會對硬體資源造成一定的浪費:如果查詢和下訂單能夠獨立部署,那麼我可以使用兩臺機器來執行查詢服務,並能剛好滿足需求;但如果是放在一起統一部署,那麼兩臺機器就未必能夠達到查詢所需的系統吞吐量,因為還有另一部分流量需要分給訂單系統,此時,可能就需要部署三臺機器

  5. 單體架構在高可用性方面也有一定的缺陷,比如,倘若資料訪問層出現問題,那麼系統中的所有業務都無法正常完成,應用程式站點也就完全失效了。但如果可以將不同的業務領域拆分開來獨立執行,或許其中一個部分無法正常工作,但其它的業務邏輯仍然可以正常工作,應用程式站點也不至於完全無法訪問。比如,線上課程系統中,使用者賬戶管理服務如果出現問題,充其量也就是新使用者無法正常註冊,老使用者暫時無法登入,但站點還是能夠正常執行,訪客仍然可以查詢課程

  6. 整個系統的技術選型從一開始就已經定好,並且在開發的過程中很難變更,即使市面上出現了更好的解決方案,也無法輕易地將新的更優秀的技術方案引入專案,因為成本會非常大。慢慢地,等應用程式功能基本完善後,或許所採用的技術已經不再主流。你或許會覺得這並不是什麼嚴重的問題,當然,不是特別嚴重,只是當新生事物出現時,我們或許會失去一些機會,比如,老的系統如何接入到雲端,以獲得自動部署、自動伸縮等優勢。到最後,或許只能用新的技術重寫整個系統,但這樣的迴圈永遠終結不了

  7. 根據不同業務場景選擇不同的基礎結構服務變得相對比較困難。比如,線上課程查詢服務可以基於Elastic Search進行快速查詢,然而使用者資訊的建立卻又無需使用Elastic Search元件,或許使用關係型資料庫會更合適。將兩套資料儲存機制都引入應用程式,又使得應用程式本身變得更為臃腫

在專案的開發過程中,我們或許還可以總結出有關單體架構的更多弱勢,在此也就不一一列舉了。這裡列出的幾條中,有不少都跟應用程式本身所立足的業務領域有關,比如希望能夠根據業務來決定系統的伸縮策略,根據業務來決定系統的技術方案等等。這裡也就給我們一個啟示:我們是不是可以根據業務將單體架構的應用程式拆開來,讓它們能夠被獨立開發、獨立運維,最後又以某種方式糅合在一起呢?當然有辦法,這就是接下來我想談談的微服務架構。

微服務架構

限於篇幅,就不在這篇文章中說微服務架構的事情了,因為肯定是說不完的。微服務架構可以說是針對單體架構的弱勢提供了完美的解決方案,然而,這並不是說單體架構就一無是處,大家可以不管三七二十一直奔微服務架構了。單體架構的魅力就在於它的簡單,如果你的應用程式沒有上面描述的這些單體架構所做不到的需求,那麼,或許繼續使用單體架構會更合適,也會讓你更舒服。當你讀完我接下來這篇討論微服務架構的文章後,或許你會同意迴歸單體架構的,因為微服務架構的成功實踐是比較困難的,不僅需要對架構整體以及獨立的微服務進行細緻的考慮和設計,而且還要求團隊有著較高的素質。事實上,已經有一些企業和專案開始從微服務架構轉向單體架構,因為各種因素使得團隊無法看到微服務架構成功實踐的曙光。

無論怎樣,軟體系統架構選擇沒有對錯,只有合適合理,甚至可以說是沒有架構:某些部分使用A技術更合適,某些部分使用B技術更合理。寫到這裡,我沒有捧高單體架構而貶低微服務架構的意思,各種架構風格都有它們適用的地方,撰寫此文也只不過是給關注架構設計的讀者做個參考。總之一句話,架構沒有銀彈。

在《為什麼Segment會從微服務退回單體架構?》一文中,有以下這段話,在此引用,也算是為下文做個鋪墊吧:

“比這還糟呢。據我觀察多數微服務架構根本就沒考慮一致性(“我們才不要亂七八糟的事務!”),盲目地隨大流還樂在其中。我搞不懂為啥子人們會覺得,把軟體模組拆分開來然後用緩慢不可靠的網路和弱爆的手動連線REST處理串起來,就能神奇地讓架構面目一新哩?我覺得人們產生這種生產力幻覺的原因是:”我把這些都搞定啦,現在我也有一套’管它是什麼即服務‘的先進玩意兒嘍!看看那酷斃的資料面板上閃爍的小綠燈吧,我們可是為了它幹了好幾個月呢!“”