從零開始學架構V2-初識架構設計-1

一、架構設計的主要目的

爲了解決軟件系統複雜度帶來的問題

二、複雜性來源

軟件的架構設計是一個非常複雜的過程;基於業務&技術現狀、公司成本、團隊規模、團隊技術能力、近三年業務發展規模預測、技術發展趨勢等條件篩選出合適的技術、編寫多種架構設計、討論並給出最終解決方案,每一步都是需要消耗大量的精力,接下來我們單從技術角度瞭解下設計出合適的架構面臨的挑戰,同時也是架構複雜性的來源。

2.1 高性能

隨着互聯網的普及伴隨着用戶的逐漸增多,從零散用戶到十萬級、千萬級、億級,對系統的性能要求也在不斷變高,根據“3秒定律”限制了系統響應速度最大不能超過一定閾值。
那該如何應對海量的請求呢?
在機器數量不變的情況下必然需要對單機性能進行優化。以服務器爲例:從apache -> nginx/tomcat可以看出一個用戶請求對應一個進程,發展到一個請求對應一個線程,再到多個用戶請求到一個線程,在業務不變情況下通過調整服務器類型也可以大幅提高併發數量。
即便單機性能優化到極致,在絕對的數量面前單機仍然無法應對排山倒海的流量,於是乎單機架構逐漸向集羣架構發展;架構的變化不能改變業務邏輯,爲此將請求的處理過程整體抽象問題計算和存儲,針對不同的分類處理方式也不一樣。
計算:接受請求並按照業務規則處理並返回結果。
存儲:將接受的請求、計算過程、處理結果等持久化到磁盤。
無論計算高性能還是存儲高性能,核心都是通過增加機器來提高整體處理請求的數量和性能;那常見實現思路是怎樣的呢?
計算的高性能思路:

  1. 非侵入式:業務邏輯通過橫向擴展機器方式,經負載均衡分發請求到多臺機器上,常見技術有NGINX、APACHE等。
  2. 侵入式:將一個請求拆分成多個處理過程,由多個機器同時處理用於解決單機性能不足的問題。

存儲的高性能思路: 將海量數據分散到多個機器中存儲,當查詢數據時通過算法/規則合併結果響應給上游,常見的實現有MySQL的分庫分表,hadoop的HDFS等。

2.2 高可用

各大互聯網公司對外宣稱自己服務的可用性爲X個9,那這背後是如何做到的呢?
爲應對海量請求的服務從單機架構轉換成集羣架構,集羣規模達到一定數量後硬件難免出現磁盤故障、機器宕機、斷網等問題;如果某個服務只有一臺機器,出現影響故障勢必影響整體業務運轉,爲了規避硬件問題帶來的不可用風險,常用的應對方案就是冗餘備份;當故障發生時需要系統自動剔除故障機器,使服務自動恢復;爲了實現此目標需要將主服務機器的全量數據同步給備用機器;主備服務之間相互檢測對方的服務狀態(啓動中、正常、故障燈)、數據變更等,這些都需要進行數據的傳輸,當兩臺機器在同一個機房,數據的傳輸速度比較快,延遲問題不是特別明顯,當跨機房、城市、國家時數據同步延遲就是一個必須考慮的問題;
簡而言之,在高可用中核心是如何降低數據的延遲、故障的自動切換,目前常見的的高可用實現方式有獨裁式、協商式、民主式,接下來我們分別來介紹下。

2.2.1 獨裁式

介紹: 獨裁式是指定一臺機器作爲獨立的決策主體,負責收集信息然後進行決策,我們姑且稱它爲“決策者”;所有冗餘的個體都將狀態信息發送給決策者,我們姑且稱它爲“上報者”。當某個上報者故障,由決策者決策哪個上報者頂替故障節點。
圖示:

獨裁式的決策方式不會出現決策混亂的問題,因爲只有一個決策者,但問題也正是在於只有一個決策者。當決策者本身故障時,整個系統就無法實現準確的狀態決策。如果爲決策者本身做一套狀態決策,那就陷入一個遞歸的死循環了。

2.2.2 協商式

事件萬物總是相對的,有獨裁就有民主,有壓迫就有反抗,哈哈。協商式是最簡單的民主式了。
民主式是兩臺及以上服務器之間相互協商選擇出一個決策者,由決策者收集客戶端請求及冗餘機器信息並作出決策,如果決策者掛了則剩餘機器重新協商選擇新的決策者;當有新的節點增加進來可能會進行新的協商得出決策者。
協商式是兩個獨立的個體通過交流信息,然後根據規則進行決策,最常用的協商式決策就是主備決策。
圖示:

此種架構啓動方式爲:

  1. 初識階段:2臺服務器狀態都是備機。
  2. 通信階段:2臺服務器建立網絡通信連接。
  3. 協商階段:2臺服務器交換狀態信息,作出決策其中一臺服務器成爲主機,另一臺繼續作爲備機。

整個過程比較容易理解,民主式的難點服務器間狀態同步、信息交換,當網絡出現問題該如何做決策。
本小節爲協商式,先看看協商式的狀態同步問題:

當主備兩臺機器之間的網絡出現故障,對外提供服務的網絡正常,此時備機是否需要頂替上去?如果頂替上去那過一會兒網絡恢復則會出現兩臺主機。如果不頂替上去,萬一主機真的是掛了則使用去了備機的作用。
這裏就不給出答案了,可以自行思考一下。

2.2.3 民主式

在協商式中已經介紹了什麼是民主式,定義就不在重複敘述了。
圖示:

民主式與協商式類似,都是通過協商來選定一個決策者,啓動方式也是三個階段,差異點在於民主式相對於協商式每個階段會複雜很多:

啓動階段 民主式 協商式
通信階段 多臺機器時間通信線路有n(n-1)/2條 只有一條
協商階段 協商算法複雜,以目前raft算法爲例相信大多數人看的也是雲裏霧裏的,更別說Paxos了;整個協商可能會進行很多輪,協商過程也比較耗時 二選一,so easy

民主式也會面對協商式的網絡的問題,其中比較出名就是“腦裂”問題。

從圖中可以看到正常狀態的時候只有一個主節點,其他節點作爲備節點;當節點D、E與A、B、C節點失去鏈接,此時A、B、C重新協商A繼續爲主對外提供服務,D、E形成了一個局域網,通過協商式單獨形成一個集羣並對外提供服務,導致整體服務出現錯亂。
業內解決腦裂的常見方式是“投票節點數必須超過系統總節點數一半”規則來處理。繼續以上圖來說,D、E形成的子集羣節點數量爲2,原總結點數量爲5,沒有達到總節點數的一半,因此這個子集羣廢棄。這種方式雖然解決了腦裂問題,但同時降低了系統整體的可用性,即如果系統不是因爲腦裂問題導致投票節點數過少,而真的是因爲節點故障(例如,A、B、C真的發生了故障),此時系統也不會選出主節點,整個系統就相當於宕機了,儘管此時還有D和E是正常的。

2.2.4 問題與思考

綜合分析,無論採取什麼樣的方案,狀態決策都不可能做到任何場景下都沒有問題,但完全不做高可用方案又會產生更大的問題,如何選取適合系統的高可用方案,也是一個複雜的分析、判斷和選擇的過程。

2.3 可擴展性

做技術的都聽過一句話“唯一不變的就是變化”,本小節要介紹的就是應對將來需求變化而提供的一種擴展能力,當有新的需求出現時,系統不需要或者僅需要少量修改就可以支持,無須整個系統重構或者重建。
想要設計出擴展性較強的系統核心就是識別變化點、封裝穩定項
以某個小功能開發舉例說明,假設現在要開發一個結算系統:普通商家按照訂單實時結算,重點商家彙總訂單金額對賬無誤後隔天結算;不同的商家類型、不同的活動、不同訂單時段、商家星級等結算時抽傭比例不同,這些維度可以任意組合,經過計算最終得出結算值。 第一步:識別變化點,找變化的部分和不變的部分。
怎麼找?怎麼感覺都是變化的部分 / 都是不變的部分呢?這確實是難點,開發過幾年技術實現應該不是大問題,難的就是一眼識破其本質的能力。簡單說一條個人經驗:一般在產品提出需求的時候會有很多提示,根據產品的提示和自己的經驗來尋找這兩個部分;看一下上面的需求,不同的商家有不同的結算方式,這裏不變的是結算類型(這裏說的是type),變化的是具體得結算類型(是指具體得type值);商家類型、訂單時間等多個維度組合得出結算的抽傭比例,計算抽傭規則是變化的,但計算過程是固定的,這樣就把變化和不變的部分找出來了。
第二步:封裝穩定項。根據不變的抽取接口,根據變化的部分抽取實現類
基於第一步可以抽象以下接口:

// 計算結算值表達式字段接口
public interface SettlementAttr{
    public Object getAttrValue(Message message);
}
public interface MerchantTypeSettlementAttr implements SettlementAttr {
    public Object getAttrValue(Message message){
        // 返回商家類型
    }
}
public interface OrderTimeSettlementAttr implements SettlementAttr {
    public Object getAttrValue(Message message){
        // 返回訂單時間
    }
}

public class SettlementRule{
    public SettleValue getSettlementValue(Message message){
        // 獲取計算抽傭值表達式
        // 通過 settlementAttr.getAttrValue 獲取表達式字段值
        // 將表達式值 代入表達式 計算得出最終抽傭值
    }
}

public interface SettlementType{
    public SettleOrder settlement(Message message);
}
public class NormalMerchant implements SettlementType{
    public SettleOrder settlement(Message message){
        // 實時結算
    }
}
public class KeyMerchantMerchant implements SettlementType{
    public SettleOrder settlement(Message message){
        // 隔天結算
    }
}

這裏的設計都是基於已有業務和可預知的變化點,如果業務上結算抽傭值就是簡單的抽傭1-10點訂單抽傭1%,重點商家再次基礎上減少0.1%,那麼上文的設計就是過度設計。當然設計不是僅限於某個場景而做的,基於某個場景而做的設計很多時候就變成了業務翻譯。當前提業務的需求結算抽傭值就是簡單的抽傭1-10點訂單抽傭1%,重點商家再次基礎上減少0.1%,但業務有說目前這塊不會太固定,需要經常修改,那直接按照if 判斷訂單時間、商家類型就是業務翻譯,毫無擴展性可言。

3.4 低成本、規模等

3.4.1 低成本

當我們的服務機器數量只有幾臺、幾十臺時,談論"低成本"投入產出比較低,但機器數量達到上千、上萬及以上時,成本就是一個值得討論的事情了。 假設一臺機器成本每年1000元,使用A方案需要2000臺機器,使用B方案則只需要1000臺機器,單從機器數量上來看成本節約了50%,換算金額每年大約節省了100萬元(4c8g),如果把這些錢發給員工,想想都是一件很爽的事情,哈哈。言歸正傳,對於技術來說也體現了技術的價值,同樣在也是精神上的一種滿足。 在前面介紹了高性能、高可用都是通過增加機器應對海量流量並提供穩定的服務,而低成本則是減少機器,他們之間有所衝突,因此在做架構設計的時候需要進行考慮到低成本這一限制約束。 在保證高性能、高可用的同時想要實現低成本,往往需要一些創新。如Java web系統早期EJB + weblogic,到ssh+tomcat來降低成本。

3.4.2 規模

當看到大學畢業設計時寫的系統、公司迭代多年的系統,要論複雜度肯定會毫不猶豫的說是公司迭代多年的系統複雜,就引出了規模複雜度之一的功能模塊越多導致整體複雜度達到質變。當有N個模塊是,最大的鏈接數量爲n(n-1)/2,文字總是不太直觀的表現複雜性,借用民主式啓動時的圖(左側),實際中的模塊數量遠不止4個。 當模塊數量達到一定數量後,複雜度呈指數級上升引起質變,這也是爲何最近幾年大家都開始推崇領域模型的原因,通過充血模型、領域設計來降低複雜度。 除了業務的複雜性外,數據的規模增長也使得系統越來越複雜。以MYSQL爲例,數據量從萬到百萬、億、百億及以上時數據的操作會有所不同,主要體現在:

  1. 數據庫的查詢。億級以上大多涉及到分庫分表,讀寫分離,當查詢分表數據時需要中間件自動解析SQL拆分多個子SQL分別去分表中查詢、合併查詢結果等;
  2. 表字段的變更。數據量越大,增加索引、修改字段都是非常耗時,表大一些可能需要幾個小時。期間MYSQL會鎖表,無法增刪改,這對業務影響非常大。
  3. 數據備份。數據越多,備份時間越長,佔用帶寬時間也較長,整體成本也較多。

3.4.3 其他

影響架構複雜的因素還有很多,例如網絡安全、公司發展階段等,就不一一列舉了。

4.架構設計原則

在網上搜索架構設計相關,會驚奇的發現每個人的設計思路差異還挺大的,相關的課程內容差異點也比較大,這是爲什麼呢?看的多了之後總結出來就是架構設計業界並沒有一套標準,個人經驗居多,這樣就是很多時候在技術方案階段,幾個人爭論應該用何種方案合適而爭論的面紅耳赤。在很多文章、課程中都提到了幾個原則感覺還不錯,在架構設計時可以少走一些彎路。

4.1 合適原則

很多技術人員都有很強的技術情結,當他們做方案或者架構時,總想不斷地挑戰自己,想達到甚至優於業界領先水平是其中一個典型表現,或新學到一門架構理念,想要在項目中大顯身手,以展示自己優秀的才能,但現實是如果不考慮目前業務的現狀、可預見的業務發展方向、規模、公司規模、團隊技術能力等,那麼設計出來的架構除了炫技術外就剩下“蹩腳”了。 所以,貼合實際場景進行設計,合適的纔是最好的。

4.2 簡單原則

相信沒人主動願意接手一個複雜系統,在滿足當下及可預見的需求,採用最簡單的架構設計方式。

4.3 演進原則

“羅馬不是一日建成的”,相信都聽說過這句話,表達了任何事物都有自己的發展運作規律,一步登天是不可能的,架構設計也是如此。 淘寶的架構迭代經過了很多次:

  1. 淘寶網剛創立時,源碼是買的,修改了一些內容後歷經一個月採用LAMP架構、單機架構快速上線。
  2. 網站註冊用戶越來越多發現數據庫是瓶頸,將數據庫切換到Oracle,後來又將後端代碼切換成Java;單機也演變成集羣架構;
  3. 通用組件拆分成分佈式架構
  4. 遷移至阿里雲;開始統一架構,從整體系統層面提高開發效率、運維標準、高可用、高性能、高擴展性、低成本等。

結合業務的現狀、可預見的業務發展方向、規模、公司規模、團隊技術能力等,只要符合這些限制條件,也能滿足未來可預見的多方面需求就可以了。

四、FAQ

  1. 這麼多需求,從哪裏開始下手進行架構設計呢? 答: 通過熟悉和理解需求,識別系統複雜性所在的地方,然後針對這些複雜點進行架構設計。
  2. 架構設計要考慮高性能、高可用、高擴展……這麼多高XX,全部設計完成估計要1個月,但老大隻給了1周時間 答: 架構設計並不是要面面俱到,不需要每個架構都具備高性能、高可用、高擴展等特點,而是要識別出複雜點然後有針對性地解決問題。
  3. 業界A公司的架構是X,B公司的方案是Y,兩個差別比較大,該參考哪一個呢? 答: 理解每個架構方案背後所需要解決的複雜點,然後才能對比自己的業務複雜點,參考複雜點相似的方案。其次,遵循這條準則能夠讓“老鳥”架構師有得放矢,而不是貪大求全。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章