1. 程式人生 > >分散式架構之系統拆分

分散式架構之系統拆分

系統拆分是單體程式向分散式系統演變的關鍵一步,也是很重要的一步,拆分的好壞直接關係到未來系統的擴充套件性、可維護性和可伸縮性等,拆分工作不難理解,但是如何正確拆分、有什麼樣的方法和原則能幫助我們拆分得到一個我們理想中的系統:高可用、可擴充套件、可維護、可伸縮的分散式系統。

MartinFowler的《重構改善既有程式碼的設計》一書給重構的定義:在不改變程式碼外在行為的前提下,對程式碼做出修改,以改程序序的內部結構。拆分也是在不改變系統行為的前提下,對系統進行各種拆解,所以可以看出拆分就是重構,是重構的一種方式。

這裡主要從三個方面來分析系統拆分工作:拆分需求、拆分原則和拆分方法;

一、拆分需求

什麼情況下系統才需要進行拆分? 當然並不是所有的系統都需要拆分,也並不是所有的系統都適合進行拆分,因為系統拆分是一個耗時、耗力又風險比較高的工作,在決定對系統進行拆分前,多問自己幾次真的需要進行拆分嗎?還有其它更好的方法嗎?

拆分的需求主要來源於下面幾個方面:

1、組織結構變化:從最初的一個團隊逐漸成長並拆分為幾個團隊,團隊按照業務線不同進行劃分,為了減少各個業務系統和程式碼間的關聯和耦合,幾個團隊不再可能共同向一個程式碼庫中提交程式碼,必須對原有系統進行拆分,以減少團隊間的干擾。

2、安全:這裡所指的安全不是系統級別的安全,而是指程式碼或成果的安全,尤其是對於很多具有核心演算法的系統,為了程式碼不被洩露,需要對相關係統進行模組化拆分,隔離核心功能,保護智慧財產權。

3、替換性:有些產品為了提供差異化的服務,需要產品具有可定製功能,根據使用者的選擇自由組合為一個完整的系統,比如一些模組,免費使用者使用的功能與收費使用者使用的功能肯定是不一樣的,這就需要這些模組具有替換性,判斷是免費使用者還是收費使用者使用不同的模組組裝,這也需要對系統進行模組化拆分。

4、交付速度:單體程式最大的問題在於系統錯綜複雜,牽一髮而動全身,也許一個小的改動就造成很多功能沒辦法正常工作,極大的降低了軟體的交付速度,因為每次改動都需要大量的迴歸測試確保每個模組都能正確工作,因為我們不清楚改動會影響到什麼,所以需要做大量重複工作,增加了測試成本。這時候就需要對系統進行拆分,理清各個功能間的關係並解耦。

5、技術需求:

1)單體程式由於技術棧固定,尤其的是比較龐大的系統,不能很方便的進行技術升級,或者說對引入新技術或框架等處於封閉狀態;每種語言都有自己的特點,單體程式沒有辦法享受到其它語言帶來的便利;對應到團隊中,團隊技術相對比較單一。

2相比於基於業務的垂直拆分,基於技術的橫向拆分也很重要,使用資料訪問層可以很好的隱藏對資料庫的直接訪問、減少資料庫連線數、增加資料使用效率等;橫向拆分可以極大的提高各個層級模組的重用性。

6、業務需求:由於業務上的某些特殊要求,比如對某個功能或模組的高可用性、高效能、可伸縮性等的要求,雖然也可以將單體整體部署到分散式環境中實現高可用、高效能等,但是從系統維護的角度來考慮,每次改動都要重新部署所有節點,顯然會增加很多潛在的風險和不確定定性因素,所以有時候不得不選擇將那些有特殊要求的功能從系統中抽取出來,獨立部署和擴充套件。

上面通過從團隊、產品、交付、技術以及業務方面分析了系統拆分的需求,從更大的範圍來看,拆分可以分為兩種:縱向和橫向。縱向拆分主要從業務角度進行,根據業務分割為不同的子系統;而橫向拆分側重於技術的分層,每個層級的技術側重點不同,可以充分發揮和培養團隊中每個人的技術特長。

二、拆分原則

有了拆分需求,下面就要對系統進行拆分了,面對動輒百萬行程式碼的系統,我們從哪裡下手呢?要注意哪些問題?等等一系列問題接踵而來,下面就介紹幾個系統拆分的基本原則,可以幫助我們快速的開始對系統進行拆分:

1、業務優先:每個系統天然都會按業務功能分成多個模組,每個模組又包含許多業務相關的功能,在系統拆分時,我們就可以優先考慮按照業務邊界進行切割,切割完成後再針對每個模組進行拆解,循序漸進,逐漸迭代深入,最終完成系統的拆解。這個過程類似庖丁解牛,要找到關節之處下刀,方能事半功倍。

2、循序漸進:系統拆分過程中包含兩個非常重要的工作:拆分和測試。二者缺一不可,並且二者是並行進行的,一定要邊拆分邊測試。每一步拆分完成都要保證系統功能是完整的,保證系統的測試是完整的。拆分要小步前進,如此以來可以減少累計錯誤的發生。這一點在《重構》這本書中也講到了。

3、兼顧技術:系統不能為了分散式而分散式,系統拆分的代價相當昂貴;當然如果有拆分的需要,我們也不能白白浪費這麼好的學習機會:

重構:拆分過程不僅僅是業務梳理的過程,也是系統進行重構的過程。通過系統的重構我們可以使用一些模式讓程式碼結構更清晰,具有更好的可讀性,並且方便日後的修改。

分層:拆分可以讓系統分解為許多功能單一的系統,這些系統可以根據需要使用不同的技術和架構進行實現,可以讓熟悉不同技術的人做不同的事,工作更高效,產品質量也可以提高。比如那些熟悉UI技術的人可以專注使用者體驗方面的研發,那些JAVA,C++方面的專家就可以把精力放在服務端程式的開發上,而那些熟悉資料庫技術的人就可以話更多精力在資料庫的優化上,術業有專攻,合適的人做合適的事。

4、可靠測試:“重構之前,首先檢查自己是否有一套可靠的測試機制,這是MartinFowler在《重構》這本書中說到的,它同樣對系統拆分有效。拆分是在對系統進行大手術,每一次的改動都要保證系統保持原來的行為不變。測試使得我有足夠的信心進行下一步的拆分或重構,不至於在錯誤的道路上越走越遠,以至於錯誤累積。測試與拆分如影隨形,每一步都要有足夠的測試。沒有測試的拆分和重構我真的不敢想象結果會是什麼樣子。

三、拆分方法

前面介紹了那麼多,都在為拆分做準備,如何進行拆分這才是重點。拆分分為資料拆分和功能拆分。資料拆分主要指資料庫的拆分和資料訪問物件的拆分。在系統拆分過程中,一般情況下我會先進行資料拆分,難後再進行功能拆分。

資料拆分

首先對資料庫按照業務進行拆分,把需要拆分的業務的相關表放到一個新的資料庫中,但是保持上層系統結構不變(如下圖)。

資料庫的拆分相對於業務的拆分來說比較容易,對系統的影響也容易判斷。拆分資料庫時,對於一些公共資料(既出現在A系統又出現在B系統或者其它系統的具有公共屬性的資料表),可以分兩步進行處理:

第一步、在AB系統中保留兩份相同的資料,注意這裡指的是具有公共屬性的表,不包括那些與業務相關的資料表,雖然業務資料也會被其它模組引用。拆分過程一定要把握好業務邊界,只有定義清晰的業務邊界才能拆分出具有清晰定義的模組,這一點非常重要。

第二步、處理公共資料,主要有兩種方式:

1、        建立公共服務模組

將這部分資料連同業務拆分為獨立的服務模組,通過API對外提供服務,在系統拆分或重構的過程中,很有必要首先將一些公共的基礎功能拆分為基礎服務。如果將系統拆分工作看作是一個主幹,那構建公共服務模組的工作就是一些分支,拆分的過程需要處理大量的公共功能,因為拆分就是要將不相關的功能分開,把相關的功能合併的過程。做完這些公共模組的工作還是要回到主幹繼續下面的工作。

2、  對於不需要抽象為公共服務的資料,可以保持一定的資料冗餘,但是冗餘的資料拷貝數不要太多,這會對資料同步提出很高的要求;根據資料的不同使用不同的資料同步機制確保各個模組中資料的一致性,至於要實現強一致性還是最終一致性要根據業務需求來定,不同的資料庫在資料一致性方面的都有相關的實現,這裡不再贅述。

第三步:建立資料服務層,在拆分後的資料庫基礎之上建立一層資料服務層,對資料庫訪問進行封裝和隱藏,對外提供資料訪問介面。

功能拆分

完成資料拆分後,就可以開始業務的拆分;拆分過程中需要堅持一個基本原則:高內聚、低耦合。拆分的實際工作就是解耦,下面主要介紹基於Proxy-Façade模式的系統拆分方法:

1)找出A模組中對其它模組的呼叫或引用,在A模組中建立一層代理(Proxy),用於代理A對其它模組的訪問,所有的外部訪問都需要通過這些代理物件實現。

2)找出所有外部模組對A模組內的類和物件的呼叫或引用,同樣在A模組內建立一些Façade類,其它模組對A模組的訪問都需要動過Façade類, Façade類隱藏了A模組的內部實現細節。

為什麼使用Proxy-Façade模式?Proxy-Façade模式的優點在哪裡?

系統拆分最重要的目標就是要實現服務化,隱藏模組內部的實現細節,以介面形式提供服務和使用服務,實現高內聚低耦合的目的。Proxy-Façade模式可以很好的滿足這一目標,Proxy將模組與外部模組的通訊進行封裝和隱藏,使用方不用關心對方的業務實現,通過Proxy就像直接與相應的模組在通訊;Façade隱藏了模組的內部實現細節,對外提供友好的介面。Proxy-Façade模式不僅可以隱藏業務細節,還可以隱藏通訊實現,使用方無需關心服務是本地呼叫還是通過RPC的遠端呼叫。Proxy-Façade模式不僅實現業務的高內聚而且也能夠保證模組間的低耦合。

至此我們已經知道了如何拆分一個系統的方法和原則,但是實際拆分工作會非常複雜和繁瑣,比如介面的膨脹化、介面的改變、重複業務的重構等;我們有時候將重構和拆分比喻為表面風平浪靜下面卻暗流湧動的海洋。