架構(gòu)設(shè)計的問題與解法

把書讀薄之『從0開始學(xué)架構(gòu)』

0、引語

小到某個功能的開發(fā)方案,大到整個業(yè)務(wù)的系統(tǒng)設(shè)計,都可以看到架構(gòu)設(shè)計的影子,但是架構(gòu)設(shè)計的目的到底是什么?『從0開始學(xué)架構(gòu)』的作者給我們的解答是:架構(gòu)設(shè)計的主要目的是為了解決軟件系統(tǒng)復(fù)雜度帶來的問題

這里其實有兩個重點:一是問題,二是解決。

首先得知道我們要解決的問題在哪里?面前的系統(tǒng)到底有什么復(fù)雜度導(dǎo)致的問題?只有知道了問題才能選擇解法,不能拿著錘子找釘子。

當(dāng)知道了當(dāng)前面臨的問題后,就要利用前人的智慧和自身的經(jīng)驗,設(shè)計出合理的架構(gòu)方案來解決問題。

因此對整本書的內(nèi)容,分成以下四節(jié),第一節(jié)主要描述我們通常面臨的系統(tǒng)復(fù)雜度及問題在哪,后面三節(jié)則是對常見的三個問題下的常用解法進(jìn)行闡述。

1、基本概念與設(shè)計方法

在講解架構(gòu)思想之前,先統(tǒng)一介紹一下基本概念的含義,避免每個人對系統(tǒng)、框架、架構(gòu)這些名詞的理解不一致導(dǎo)致的誤解。下面是作者對每個名詞的定義,其作用域僅限本文范疇,不用糾結(jié)其在其他上下文中的意義。

  • 系統(tǒng):系統(tǒng)泛指由一群有關(guān)聯(lián)的個體組成,根據(jù)某種規(guī)則運作,能完成個別元件不能單獨完成的工作的群體。
  • 子系統(tǒng):子系統(tǒng)也是由一群有關(guān)聯(lián)的個體所組成的系統(tǒng),多半會是更大系統(tǒng)中的一部分。
  • 模塊:從業(yè)務(wù)邏輯的角度來拆分系統(tǒng)后,得到的單元就是“模塊”。劃分模塊的主要目的是職責(zé)分離。
  • 組件:從物理部署的角度來拆分系統(tǒng)后,得到的單元就是“組件”。劃分組件的主要目的是單元復(fù)用。
  • 框架:是一整套開發(fā)規(guī)范,是提供基礎(chǔ)功能的產(chǎn)品。
  • 架構(gòu):關(guān)注的是結(jié)構(gòu),是某一套開發(fā)規(guī)范下的具體落地方案,包括各個模塊之間的組合關(guān)系以及它們協(xié)同起來完成功能的運作規(guī)則。

由以上定義可見,所謂架構(gòu),是為了解決軟件系統(tǒng)的某個復(fù)雜度帶來的具體問題,將模塊和組件以某種方式有機(jī)組合,基于某個具體的框架實現(xiàn)后的一種落地方案。

而討論架構(gòu)時,往往只討論到系統(tǒng)與子系統(tǒng)這個頂層的架構(gòu)。

可見,要進(jìn)行架構(gòu)選型,首先應(yīng)該知道自己要解決的業(yè)務(wù)和系統(tǒng)復(fù)雜點在哪里,是作為秒殺系統(tǒng)有瞬間高并發(fā),還是作為金融科技有極高的數(shù)據(jù)一致性和可用性要求等。

一般來說,系統(tǒng)的復(fù)雜度來源有以下幾個方面:

高性能:

如果業(yè)務(wù)的訪問頻率或?qū)崟r性要求較高,則會對系統(tǒng)提出高性能的要求。

如果是單機(jī)系統(tǒng),需要利用多進(jìn)程、多線程技術(shù)。

如果是集群系統(tǒng),則還涉及任務(wù)拆分、分配與調(diào)度,多機(jī)器狀態(tài)管理,機(jī)器間通信,當(dāng)單機(jī)性能達(dá)到瓶頸后,即使繼續(xù)加機(jī)器也無法繼續(xù)提升性能,還是要針對單個子任務(wù)進(jìn)行性能提升。

高可用:

如果業(yè)務(wù)的可用性要求較高,也會帶來高可用方面的復(fù)雜度。高可用又分為計算高可用和存儲高可用。

針對計算高可用,可以采用主備(冷備、溫備、熱備)、多主的方式來冗余計算能力,但會增加成本、可維護(hù)性方面的復(fù)雜度。

針對存儲高可用,同樣是增加機(jī)器來冗余,但這也會帶來多機(jī)器導(dǎo)致的數(shù)據(jù)不一致問題,如遇到延遲、中斷、故障等情況。難點在于怎么減少數(shù)據(jù)不一致對業(yè)務(wù)的影響。

既然主要解決思路是增加機(jī)器來做冗余,那么就涉及到了狀態(tài)決策的問題。即如果判斷當(dāng)前主機(jī)的狀態(tài)是正常還是異常,以及異常了要如何采取行動(比如切換哪臺做主機(jī))。

對主機(jī)狀態(tài)的判斷,多采用機(jī)器信息采集或請求響應(yīng)情況分析等手段,但又會產(chǎn)生采集信息這一條通信鏈路本身是否正常的問題,下文會具體展開討論。事實上,狀態(tài)決策本質(zhì)上不可能做到完全正確。

而對于決策方式,有以下幾種方式:

  • 獨裁式:存在一個獨立的決策主體來收集信息并決定主機(jī),這樣的策略不會混亂,但這個主體本身存在單點問題。
  • 協(xié)商式:兩臺備機(jī)通過事先指定的規(guī)則來協(xié)商決策出主機(jī),規(guī)則雖然簡單方便,但是如果兩臺備機(jī)之間的協(xié)商鏈路中斷了,決策起來就會很困難,比如有網(wǎng)絡(luò)延遲且機(jī)器未故障、網(wǎng)絡(luò)中斷且機(jī)器未故障、網(wǎng)絡(luò)中斷其機(jī)器已故障,多種情況需要處理。
  • 民主式:如果有多臺備機(jī),可以使用選舉算法來投票出主機(jī),比如Paxos就是一種選舉算法,這種算法大多數(shù)都采取多數(shù)取勝的策略,算法本身較為復(fù)雜,且如果像協(xié)商式一樣出現(xiàn)連接中斷,就會腦裂,不同部分會各自決策出不同結(jié)果,需要規(guī)避。

可擴(kuò)展性:

眾所周知在互聯(lián)網(wǎng)行業(yè)只有變化才是永遠(yuǎn)不變的,而開發(fā)一個系統(tǒng)基本都不是一蹴而就的,那應(yīng)該如何為系統(tǒng)的未來可能性進(jìn)行設(shè)計來保持可擴(kuò)展性呢?

這里首先要明確的一個觀點就是,在做系統(tǒng)設(shè)計時,既不可能完全不考慮可擴(kuò)展性,也不可能每個設(shè)計點都考慮可擴(kuò)展性,前者很明顯,后者則是為了避免舍本逐末,為了擴(kuò)展而擴(kuò)展,實際上可能會為不存在的預(yù)測花費過多的精力。

那么怎么考慮系統(tǒng)的未來可能性從而做出相應(yīng)的可擴(kuò)展性設(shè)計呢?這里作者給出了一個方法:只預(yù)測兩年內(nèi)可能的變化,不要試圖預(yù)測五年乃至十年的變化。因為對于變化快的行業(yè)來說,預(yù)測兩年已經(jīng)足夠遠(yuǎn)了,再多就可能計劃趕不上變化。而對變化慢的行業(yè),則預(yù)測的意義更是不大。

要應(yīng)對變化,主要是將變與不變分隔開來。

這里可以針對業(yè)務(wù),提煉變化層和穩(wěn)定層,通過變化層將變化隔離。比如通過一個DAO服務(wù)來對接各種變化的存儲載體,但是上層穩(wěn)定的邏輯不用知曉當(dāng)前采用何種存儲,只需按照固定的接口訪問DAO即可獲取數(shù)據(jù)。

也可以將一些實現(xiàn)細(xì)節(jié)剝離開來,提煉出抽象層,僅在實現(xiàn)層去封裝變化。比如面對運營上經(jīng)常變化的業(yè)務(wù)規(guī)則,可以提煉出一個規(guī)則引擎來實現(xiàn)核心的抽象邏輯,而具體的規(guī)則實現(xiàn)則可以按需增加。

如果是面對一個舊系統(tǒng)的維護(hù),接到了新的重復(fù)性需求,而舊系統(tǒng)并不支持較好的可擴(kuò)展性,這時是否需要花費時間精力去重構(gòu)呢?作者也提出了《重構(gòu)》一書中提到的原則:事不過三,三則重構(gòu)。

簡而言之,不要一開始就考慮復(fù)雜的做法去滿足可擴(kuò)展性,而是等到第三次遇到類似的實現(xiàn)時再來重構(gòu),重構(gòu)的時候采取上述說的隔離或者封裝的方案。這一原則對

這一原則對新系統(tǒng)開發(fā)也是適用的。總而言之就是,不要為難以預(yù)測的未來去過度設(shè)計,為明確的未來保留適量的可擴(kuò)展性即可。

低成本:

上面說的高性能、高可用都需要增加機(jī)器,帶來的是成本的增加,而很多時候研發(fā)的預(yù)算是有限的。換句話說,低成本往往并不是架構(gòu)設(shè)計的首要目標(biāo),而是設(shè)計架構(gòu)時的約束限制。

那如何在有限的成本下滿足復(fù)雜性要求呢?往往只有“創(chuàng)新”才能達(dá)到低成本的目標(biāo)。舉幾個例子:

  • NoSQL的出現(xiàn)是為解決關(guān)系型數(shù)據(jù)庫應(yīng)對高并發(fā)的問題。
  • 全文搜索引擎的出現(xiàn)是為解決數(shù)據(jù)庫like搜索效率的問題。
  • Hadoop的出現(xiàn)是為解決文件系統(tǒng)無法應(yīng)對海量數(shù)據(jù)存儲與計算的問題。
  • Facebook的HipHop PHP和HHVM的出現(xiàn)是為解決PHP運行低效問題。
  • 新浪微博引入SSD Cache做L2緩存是為解決Redis高成本、容量小、穿透DB的問題。
  • Linkedin引入Kafka是為解決海量事件問題。

上述案例都是為了在不顯著增加成本的前提下,實現(xiàn)系統(tǒng)的目標(biāo)。

這里還要說明的是,創(chuàng)造新技術(shù)的復(fù)雜度本身就是很高的,因此一般中小公司基本都是靠引入現(xiàn)有的成熟新技術(shù)來達(dá)到低成本的目標(biāo);而大公司才更有可能自己去創(chuàng)造新的技術(shù)來達(dá)到低成本的目標(biāo),因為大公司才有足夠的資源、技術(shù)和時間去創(chuàng)造新技術(shù)。

安全:

安全是一個研發(fā)人員很熟悉的目標(biāo),從整體來說,安全包含兩方面:功能安全和架構(gòu)安全。

功能安全是為了“防小偷”,即避免系統(tǒng)因安全漏洞而被竊取數(shù)據(jù),如SQL注入。常見的安全漏洞已經(jīng)有很多框架支持,所以更建議利用現(xiàn)有框架的安全能力,來避免重復(fù)開發(fā),也避免因自身考慮不夠全面而遺漏。在此基礎(chǔ)上,仍需持續(xù)攻防來完善自身的安全。

架構(gòu)安全是為了“防強(qiáng)盜“,即避免系統(tǒng)被暴力攻擊導(dǎo)致系統(tǒng)故障,比如DDOS攻擊。這里一方面只能通過防火墻集運營商或云服務(wù)商的大帶寬和流量清洗的能力進(jìn)行防范,另一方面也需要做好攻擊發(fā)現(xiàn)與干預(yù)、恢復(fù)的能力。

規(guī)模:

架構(gòu)師在宣講時往往會先說自己任職和設(shè)計過的大型公司的架構(gòu),這是因為當(dāng)系統(tǒng)的規(guī)模達(dá)到一定程度后,復(fù)雜度會發(fā)生質(zhì)的變化,即所謂量變引起質(zhì)變。

這個量,體現(xiàn)在訪問量、功能數(shù)量及數(shù)據(jù)量上。

訪問量映射到對高性能的要求。功能數(shù)量需要視具體業(yè)務(wù)會帶來不同的復(fù)雜度。而數(shù)據(jù)量帶來的收集、加工、存儲、分析方面的挑戰(zhàn),現(xiàn)有的方案基本都是給予Google的三篇大數(shù)據(jù)論文的理論:

  • Google File System 是大數(shù)據(jù)文件存儲的技術(shù)理論
  • Google Bigtable 是列式數(shù)據(jù)存儲的技術(shù)理論
  • Google MapReduce 是大數(shù)據(jù)運算的技術(shù)理論

經(jīng)過上面的分析可以看到,復(fù)雜度來源很多,想要一一應(yīng)對,似乎會得到一個復(fù)雜無比的架構(gòu),但對于架構(gòu)設(shè)計來說,其實剛開始設(shè)計時越簡單越好,只要能解決問題,就可以從簡單開始再慢慢去演化,對應(yīng)的是下面三條原則:

  1. 合適原則:不需要一開始就挑選業(yè)界領(lǐng)先的架構(gòu),它也許優(yōu)秀,但可能不那么適合自己,比如有很多目前用不到的能力或者大大超出訴求從而增加很多成本。其實更需要考慮的是合理地將資源整合在一起發(fā)揮出最大功效,并能夠快速落地。
  2. 簡單原則:有時候為了顯示出自身的能力,往往會在一開始就將系統(tǒng)設(shè)計得非常復(fù)雜,復(fù)雜可能代表著先進(jìn),但更可能代表著“問題”,組件越多,就越可能出故障,越可能影響關(guān)聯(lián)著的組件,定位問題也更加困難。其實只要能夠解決訴求即可。
  3. 演化原則:不要妄想一步到位,沒有人可以準(zhǔn)確預(yù)測未來所有發(fā)展,軟件不像建筑,變化才是主題。架構(gòu)的設(shè)計應(yīng)該先滿足業(yè)務(wù)需求,適當(dāng)?shù)念A(yù)留擴(kuò)展性,然后在未來的業(yè)務(wù)發(fā)展中再不斷地迭代,保留有限的設(shè)計,修復(fù)缺陷,改正錯誤,去除無用部分。這也是重構(gòu)、重寫的價值所在。

即使是QQ、淘寶這種如今已經(jīng)非常復(fù)雜的系統(tǒng),剛開始時也只是一個簡單的系統(tǒng),甚至淘寶都是直接買來的系統(tǒng),隨著業(yè)務(wù)發(fā)展也只是先加服務(wù)器、引入一些組件解決性能問題,直到達(dá)到瓶頸才去重構(gòu)重寫,重新在新的復(fù)雜度要求下設(shè)計新的架構(gòu)。

明確了設(shè)計原則后,當(dāng)面對一個具體的業(yè)務(wù),可以按照如下步驟進(jìn)行架構(gòu)設(shè)計:

  1. 識別復(fù)雜度:無論是新設(shè)計一個系統(tǒng)還是接手一個混亂的系統(tǒng),第一步都是先將主要的復(fù)雜度問題列出來,然后根據(jù)業(yè)務(wù)、技術(shù)、團(tuán)隊等綜合情況進(jìn)行排序,優(yōu)先解決當(dāng)前面臨的最主要的復(fù)雜度問題。復(fù)雜度的主要來源上文已經(jīng)說過,可以按照經(jīng)驗或者排查法進(jìn)行分析。
  2. 方案對比:先看看業(yè)界是否有類似的業(yè)務(wù),了解他們是怎么解決問題的,然后提出3~5個備選方案,不要只考慮做一個最優(yōu)秀的方案,一個人的認(rèn)知范圍常常是有限的,逼自己多思考幾個方案可以有效規(guī)避因為思維狹隘導(dǎo)致的局限性,當(dāng)然也不要過多,不用給出非常詳細(xì)的方案,太消耗精力。備選方案的差異要比較明顯,才有擴(kuò)寬思路和對比的價值。
  3. 設(shè)計詳細(xì)方案:當(dāng)多個方案對比得出最終選擇后,就可以對目標(biāo)方案進(jìn)行詳細(xì)的設(shè)計,關(guān)鍵細(xì)節(jié)需要比較深入,如果方案本身很復(fù)雜,也可以采取分步驟、分階段、分系統(tǒng)的實現(xiàn)方式來降低實現(xiàn)復(fù)雜度。當(dāng)方案非常龐大的時候,可以匯集一個團(tuán)隊的智慧和經(jīng)驗來共同設(shè)計,防止因架構(gòu)師的思維盲區(qū)導(dǎo)致問題。

2、高性能架構(gòu)模式

2.1、存儲高性能

互聯(lián)網(wǎng)業(yè)務(wù)大多是數(shù)據(jù)密集型的業(yè)務(wù),其對性能的壓力也常常來自于海量用戶對數(shù)據(jù)的高頻讀寫壓力上,因此解決高性能問題,首先要解決數(shù)據(jù)讀寫的存儲高性能問題。

讀寫分離:

在大多數(shù)業(yè)務(wù)中,用戶查詢和修改數(shù)據(jù)的頻率是不同的,甚至是差別很大的,大部分情況下都是讀多寫少的,因此可以將對數(shù)據(jù)的讀和寫操作分開對待,對壓力更大的讀操作提供額外的機(jī)器分擔(dān)壓力,這就是讀寫分離。

讀寫分離的基本實現(xiàn)是搭建數(shù)據(jù)庫的主從集群,根據(jù)需要提供一主一從或一主多從。

注意是主從不是主備,從和備的差別在于從機(jī)是要干活的。

通常在讀多寫少的情況下,主機(jī)負(fù)責(zé)讀寫操作,從機(jī)只負(fù)責(zé)讀操作,負(fù)責(zé)幫主機(jī)分擔(dān)讀操作的壓力。而數(shù)據(jù)會通過復(fù)制機(jī)制(如全同步、半同步、異步)同步到從機(jī),每臺服務(wù)器都有所有業(yè)務(wù)數(shù)據(jù)。

既然有數(shù)據(jù)的同步,就一定存在復(fù)制延遲導(dǎo)致的從機(jī)數(shù)據(jù)不一致問題,針對這個問題有幾種常見的解法,如:

  • 寫操作后同一用戶一段時間內(nèi)的讀操作都發(fā)給主機(jī),避免數(shù)據(jù)還沒同步到從機(jī),但這個邏輯容易遺漏。
  • 讀從機(jī)失敗后再讀一次主機(jī),該方法只能解決新數(shù)據(jù)未同步的問題,無法解決舊數(shù)據(jù)修改的問題(不會讀取失?。?,且二次讀取主機(jī)會給主機(jī)帶來負(fù)擔(dān),容易被針對性攻擊。
  • 關(guān)鍵讀寫操作全部走主機(jī),從機(jī)僅負(fù)責(zé)非關(guān)鍵鏈路的讀,該方法是基于保障關(guān)鍵業(yè)務(wù)的思路。

除了數(shù)據(jù)同步的問題之外,只要涉及主從機(jī)同時支持業(yè)務(wù)訪問的,就一定需要制定請求分配的機(jī)制。上面說的幾個問題解法也涉及了一些分配機(jī)制的細(xì)節(jié)。具體到分配機(jī)制的實現(xiàn)來說,有兩種思路:

  • 程序代碼封裝:實現(xiàn)簡單,可對業(yè)務(wù)定制化,但每個語言都要自己實現(xiàn)一次,且很難做到同步修改,因此適合小團(tuán)隊。
  • 中間件封裝:獨立出一套系統(tǒng)管理讀寫的分配,對業(yè)務(wù)透明,兼容SQL協(xié)議,業(yè)務(wù)服務(wù)器就無需做額外修改適配。需要支持多語言、完整的SQL語法,涉及很多細(xì)節(jié),容易出BUG,且本身是個單點,需要特別保障性能和可用性,因此適合大公司。

分庫分表:

除了高頻訪問的壓力,當(dāng)數(shù)據(jù)量大了以后,也會帶來數(shù)據(jù)庫存儲方面的壓力。此時就需要考慮分庫分表的問題。分庫分表既可以緩解訪問的壓力,也可以分散存儲的壓力。

先說分庫,所謂分庫,就是指業(yè)務(wù)按照功能、模塊、領(lǐng)域等不同,將數(shù)據(jù)分散存儲到不同的數(shù)據(jù)庫實例中。

比如原本是一個MySQL數(shù)據(jù)庫實例,在庫中按照不同業(yè)務(wù)建了多張表,大體可以歸類為A、B兩個領(lǐng)域的數(shù)據(jù)?,F(xiàn)在新建一個庫,將原庫中A領(lǐng)域的數(shù)據(jù)遷移到新的庫中存儲,還是按需建表,而B領(lǐng)域的數(shù)據(jù)繼續(xù)留在原庫中。

分庫一方面可以緩解訪問和存儲的壓力,另一方面也可以增加抗風(fēng)險能力,當(dāng)一個庫出問題后,另一個庫中的數(shù)據(jù)并不會受到影響,而且還能分開管理權(quán)限。

但分庫也會帶來一些問題,原本同一個庫中的不同表可以方便地進(jìn)行聯(lián)表查詢,分庫后則會變得很復(fù)雜。由于數(shù)據(jù)在不同的庫中,當(dāng)要操作兩個庫中的數(shù)據(jù)時,無法使用事務(wù)操作,一致性也變得更難以保障。而且當(dāng)增加備庫來保障可用性的時候,成本是成倍增加的。

基于以上問題,初創(chuàng)的業(yè)務(wù)并不建議在一開始就做這種拆分,會增加很多開發(fā)時的成本和復(fù)雜度,拖慢業(yè)務(wù)的節(jié)奏。

再說分表,所謂分表,就是將原本存儲在一張表里的數(shù)據(jù),按照不同的維度,拆分成多張表來存儲。

按照訴求與業(yè)務(wù)的特性不同,可以采用垂直分表或水平分表的方式。

垂直分表相當(dāng)于垂直地給原表切了一刀,把不同的字段拆分到不同的子表中,這樣拆分后,原本訪問一張表可以獲取的所有字段,現(xiàn)在則需要訪問不同的表獲取。

垂直分表適合將表中某些不常用又占了大量空間的列(字段)拆分出去,可以提升訪問常用字段的性能。

但相應(yīng)的,當(dāng)真的需要的字段處于不同表中時,或者要新增記錄存儲所有字段數(shù)據(jù)時,要操作的表變多了。

水平分表相當(dāng)于橫著給原表切了一刀,那么原表中的記錄會被分散存儲到不同的子表中,但是每張子表的字段都是全部字段。

水平分表適合表的量級很大以至影響訪問性能的場景,何時該拆分并沒有絕對的指標(biāo),一般記錄數(shù)超過千萬時就需要警覺了。

不同于垂直分表依然能訪問到所有記錄,水平分表后無法再在一張表中訪問所有數(shù)據(jù)了,因此很多查詢操作會受到影響,比如join操作就需要多次查詢后合并結(jié)果,count操作也需要計算多表的結(jié)果后相加,如果經(jīng)常用到count的總數(shù),可以額外維護(hù)一個總數(shù)表去更新,但也會帶來數(shù)據(jù)一致性的問題。

值得特別提出的是范圍查詢,原本的一張表可以通過范圍查詢到的數(shù)據(jù),分表后也需要多次查詢后合并數(shù)據(jù),如果是業(yè)務(wù)經(jīng)常用到的范圍查詢,那建議干脆就按照這種方式來分表,這也是分表的路由方式之一:范圍路由。

所謂路由方式是指:分表后當(dāng)新插入記錄時,如何判斷該往哪張表插入。常用的插入方式有以下三種:

  • 范圍路由:按照時間范圍、ID范圍或者其他業(yè)務(wù)常用范圍字段路由。這種方式在擴(kuò)充新的表時比較方便,直接加表給新范圍的數(shù)據(jù)插入即可,但是數(shù)量和冷熱分布可能是不均勻的。
  • Hash路由:根據(jù)Hash運算來路由新記錄插入的表,這種方式需要提前就規(guī)劃好分多少張表,才能決定Hash運算方式,但表數(shù)量其實很難預(yù)估,導(dǎo)致未來需要擴(kuò)充新表時很麻煩,但數(shù)據(jù)在不同表中的分布是比較均勻的。
  • 配置路由:新增一個路由表來記錄數(shù)據(jù)id和表id的映射,按照自定義的方式隨時修改映射規(guī)則,設(shè)計簡單,擴(kuò)充新表也很方便,但每次操作表都需要額外操作一次路由表,其本身也成為了單點瓶頸。

無論是垂直分表還是水平分表,單表切分為多表后,新的表即使在同一個數(shù)據(jù)庫服務(wù)器中,也可能帶來可觀的性能提升,如果性能能夠滿足業(yè)務(wù)要求,可以不拆分到多臺數(shù)據(jù)庫服務(wù)器,畢竟分庫也會引入很多復(fù)雜性的問題;如果單表拆分為多表后,單臺服務(wù)器依然無法滿足性能要求,那就不得不再次進(jìn)行業(yè)務(wù)分庫的設(shè)計了。

NoSQL數(shù)據(jù)庫:

上面發(fā)分庫分表討論的都是關(guān)系型數(shù)據(jù)庫的優(yōu)化方案,但關(guān)系型數(shù)據(jù)庫也有其無法規(guī)避的缺點,比如無法直接存儲某種結(jié)構(gòu)化的數(shù)據(jù)、擴(kuò)展表結(jié)構(gòu)時會鎖表影響線上性能、大數(shù)據(jù)場景下I/O較高、全文搜索的功能比較弱等。

基于這些缺點,也有很多新的數(shù)據(jù)庫框架被創(chuàng)造出來,解決其某方面的問題。

比如以Redis為代表的的KV存儲,可以解決無法存儲結(jié)構(gòu)化數(shù)據(jù)的問題;以MongoDB為代表的的文檔數(shù)據(jù)庫可以解決擴(kuò)展表結(jié)構(gòu)被強(qiáng)Schema約束的問題;以HBase為代表的的列式數(shù)據(jù)庫可以解決大數(shù)據(jù)場景下的I/O問題;以ES為代表的的全文搜索引擎可以解決全文檢索效率的問題等。

這些數(shù)據(jù)庫統(tǒng)稱為NoSQL數(shù)據(jù)庫,但NoSQL并不是全都不能寫SQL,而是Not Only SQL的意思。

NoSQL數(shù)據(jù)庫除了聚焦于解決某方面的問題以外也會有其自身的缺點,比如Redis沒有支持完整的ACID事務(wù)、列式存儲在更新一條記錄的多字段時性能較差等。因此并不是說使用了NoSQL就能一勞永逸,更多的是按需取用,解決業(yè)務(wù)面臨的問題。

關(guān)于NoSQL的更多了解,也可以看看《NoSQL精粹》這本書。

緩存:

如果NoSQL也解決不了業(yè)務(wù)的高性能訴求,那么或許你需要加點緩存。

緩存最直接的概念就是把常用的數(shù)據(jù)存在內(nèi)存中,當(dāng)業(yè)務(wù)請求來查詢的時候直接從內(nèi)存中拿出來,不用重新去數(shù)據(jù)庫中按條件查詢,也就省去了大量的磁盤IO時間。

一般來說緩存都是通過Key-Value的方式存儲在內(nèi)存中,根據(jù)存儲的位置,分為單機(jī)緩存和集中式緩存。單機(jī)緩存就是存在自身服務(wù)器所在的機(jī)器上,那么勢必會有不同機(jī)器數(shù)據(jù)可能不一致,或者重復(fù)緩存的問題,要解決可以使用查詢內(nèi)容做路由來保障同一記錄始終到同一臺機(jī)器上查詢緩存。集中式緩存則是所有服務(wù)器都去一個地方查緩存,會增加一些調(diào)用時間。

緩存可以提升性能是很好理解的,但緩存同樣有著他的問題需要應(yīng)對或規(guī)避。數(shù)據(jù)時效性是最容易想到的問題,但也可以靠同時更新緩存的策略來保障數(shù)據(jù)的時效性,除此之外還有其他幾個常見的問題。

如果某條數(shù)據(jù)不存在,緩存中勢必查不到對應(yīng)的KEY,從而就會請求數(shù)據(jù)庫確認(rèn)是否有新增加這條數(shù)據(jù),如果始終沒有這條數(shù)據(jù),而客戶端又反復(fù)頻繁地查詢這條數(shù)據(jù),就會變相地對數(shù)據(jù)庫造成很大的壓力,換句話說,緩存失去了保護(hù)作用,請求穿透到了數(shù)據(jù)庫,這稱為緩存穿透

應(yīng)對緩存穿透,最好的手段就是把“空值”這一情況也緩存下來,當(dāng)客戶端下次再查詢時,發(fā)現(xiàn)緩存中說明了該數(shù)據(jù)是空值,則不會再問詢數(shù)據(jù)庫。但也要注意如果真的有對應(yīng)數(shù)據(jù)寫入了數(shù)據(jù)庫,應(yīng)當(dāng)能及時清除”空值“緩存。

為了保障緩存的數(shù)據(jù)及時更新,常常都會根據(jù)業(yè)務(wù)特性設(shè)置一個緩存過期時間,在緩存過期后,到再次生成期間,如果出現(xiàn)大量的查詢,會導(dǎo)致請求都傳遞到數(shù)據(jù)庫,而且會多次重復(fù)生成緩存,甚至可能拖垮整個系統(tǒng),這就叫緩存雪崩,和緩存穿透的區(qū)別在于,穿透是面對空值的情況,而雪崩是由于緩存重新生成的間隔期大量請求產(chǎn)生的連鎖效應(yīng)。

既然是緩存更新時重復(fù)生成所導(dǎo)致的問題,那么一種解法就是在緩存重新生成前給這個KEY加鎖,加鎖期間出現(xiàn)的請求都等待或返回默認(rèn)值,而不去都嘗試重新生成緩存。

另一種方法是干脆不要由客戶端請求來觸發(fā)緩存更新,而是由后臺腳本統(tǒng)一更新,同樣可以規(guī)避重復(fù)請求導(dǎo)致的重復(fù)生成。但是這就失去了只緩存熱點數(shù)據(jù)的能力,如果緩存因空間問題被清除了,也會因為后臺沒及時更新導(dǎo)致查不到緩存數(shù)據(jù),這就會要求更復(fù)雜的后臺更新策略,比如主動查詢緩存有效性、緩存被刪后通知后臺主動更新等。

雖說在有限的內(nèi)存空間內(nèi)最好緩存熱點數(shù)據(jù),但如果數(shù)據(jù)過熱,比如微博的超級熱搜,也會導(dǎo)致緩存服務(wù)器壓力過大而崩潰,稱之為緩存熱點問題。

可以復(fù)制多份緩存副本,來分散緩存服務(wù)器的單機(jī)壓力,畢竟堆機(jī)器是最簡單有效。此處也要注意,多個緩存副本不要設(shè)置相同的緩存過期時間,否則多處緩存同時過期,并同時更新,也容易引起緩存雪崩,應(yīng)該設(shè)置一個時間范圍內(nèi)的隨機(jī)值來更新緩存。

2.2、計算高性能

講完存儲高性能,再講計算高性能,計算性能的優(yōu)化可以先從單機(jī)性能優(yōu)化開始,多進(jìn)程、多線程、IO多路復(fù)用、異步IO等都存在很多可以優(yōu)化的地方,但基本系統(tǒng)或框架已經(jīng)提供了基本的優(yōu)化能力,只需使用即可。

負(fù)載均衡:

如果單機(jī)的性能優(yōu)化已經(jīng)到了瓶頸,無法應(yīng)對業(yè)務(wù)的增長,就會開始增加服務(wù)器,構(gòu)建集群。對于計算來說,每一臺服務(wù)器接到同樣的輸入,都應(yīng)該返回同樣的輸出,當(dāng)服務(wù)器從單臺變成多臺之后,就會面臨請求來了要由哪一臺服務(wù)器處理的問題,我們當(dāng)然希望由當(dāng)前比較空閑的服務(wù)器去處理新的請求,這里對請求任務(wù)的處理分配問題,就叫負(fù)載均衡

負(fù)載均衡的策略,從分類上來說,可以分為三類:

  • DNS負(fù)載均衡:通過DNS解析,來實現(xiàn)地理級別的均衡,其成本低,分配策略很簡單,可以就近訪問來提升訪問速度,但DNS的緩存時間長,由于更新不及時所以無法快速調(diào)整,且控制權(quán)在各域名商下,且無法根據(jù)后端服務(wù)器的狀態(tài)來決定分配策略。
  • 硬件負(fù)載均衡:直接通過硬件設(shè)備來實現(xiàn)負(fù)載均衡,類似路由器路由,功能和性能都很強(qiáng)大,可以做到百萬并發(fā),也很穩(wěn)定,支持安全防護(hù)能力,但是同樣無法根據(jù)后端服務(wù)器狀態(tài)進(jìn)行策略調(diào)整,且價格昂貴。
  • 軟件負(fù)載均衡:通過軟件邏輯實現(xiàn),比如nginx,比較靈活,成本低,但是性能一般,功能也不如硬件強(qiáng)大。

一般來說,DNS 負(fù)載均衡用于實現(xiàn)地理級別的負(fù)載均衡;硬件負(fù)載均衡用于實現(xiàn)集群級別的負(fù)載均衡;軟件負(fù)載均衡用于實現(xiàn)機(jī)器級別的負(fù)載均衡。

所以部署起來可以按照這三層去部署,第一層通過DNS將請求分發(fā)到北京、上海、深圳的機(jī)房;第二層通過硬件負(fù)載均衡將請求分發(fā)到當(dāng)?shù)厝齻€集群中的一個;第三層通過軟件策略將請求分發(fā)到具體的某臺服務(wù)器去響應(yīng)業(yè)務(wù)。

就負(fù)載均衡算法來說,多是我們很熟悉的算法,如輪詢、加權(quán)輪詢、負(fù)載最低優(yōu)先、性能最優(yōu)優(yōu)先、Hash分配等,各有特點,按需采用即可。

3、高可用架構(gòu)模式

3.1、理論方法

CAP與BASE:

在說高可用之前,先來說說CAP理論,即:

在一個分布式系統(tǒng)(指互相連接并共享數(shù)據(jù)的節(jié)點的集合)中,當(dāng)涉及讀寫操作時,只能保證一致性(Consistence)、可用性(Availability)、分區(qū)容錯性(Partition Tolerance)三者中的兩個,另外一個必須被犧牲。

大家可能都知道CAP定理是什么,但大家可能不知道,CAP定理的作者(Seth Gilbert & Nancy Lynch)其實并沒有詳細(xì)解釋CAP三個單詞的具體含義,目前大家熟悉的解釋其實是另一個人(Robert Greiner)給出的。而且他還給出了兩版有所差異的解釋。

第二版解釋算是對第一版解釋的加強(qiáng),他要加強(qiáng)的點主要是:

  • CAP描述的分布式系統(tǒng),是互相連結(jié)并共享數(shù)據(jù)的節(jié)點的集合。因為其實并不是所有的分布式系統(tǒng)都會互連和共享數(shù)據(jù)。
  • CAP理論是在涉及讀寫操作的場景下的理論,而不是分布式系統(tǒng)的所有功能。
  • 一致性只需要保障客戶端讀操作能讀到最新的寫操作結(jié)果,并不要求時時刻刻分布式系統(tǒng)的數(shù)據(jù)都是一致的,這是不現(xiàn)實的,只要保障客戶讀到的一致即可。
  • 可用性要求非故障的節(jié)點在合理的時間內(nèi)能返回合理的響應(yīng),所謂合理是指非錯誤、非超時,即使數(shù)據(jù)不是最新的數(shù)據(jù),也是合理的“舊數(shù)據(jù)”,是符合可用性的。
  • 分區(qū)容錯性要求網(wǎng)絡(luò)分區(qū)后系統(tǒng)能繼續(xù)履行職責(zé),不僅僅要求系統(tǒng)不宕機(jī),還要求能發(fā)揮作用,能處理業(yè)務(wù)邏輯。比如接口直接返回錯誤其實也代表系統(tǒng)在運行,但卻沒有履行職責(zé)。

在分布式系統(tǒng)下,P(分區(qū)容忍)是必須選擇的,否則當(dāng)分區(qū)后系統(tǒng)無法履行職責(zé)時,為了保障C(一致性),就要拒絕寫入數(shù)據(jù),也就是不可用了。

在此基礎(chǔ)上,其實我們能選擇的只有C+P或者A+P,根據(jù)業(yè)務(wù)特性來選擇要優(yōu)先保障一致性還是可用性。

在選擇保障策略時,有幾個需要注意的點:

  • CAP關(guān)注的其實是數(shù)據(jù)的粒度,而不是整個系統(tǒng)的粒度,因此對于系統(tǒng)內(nèi)的不同數(shù)據(jù)(對應(yīng)不同子業(yè)務(wù)),其實是可以按照業(yè)務(wù)特性采取不同的CAP策略的。
  • CAP實際忽略了網(wǎng)絡(luò)延遲,也就是允許數(shù)據(jù)復(fù)制過程中的短時間不一致,如果某些業(yè)務(wù)比如金融業(yè)務(wù)無法容忍這一點,那就只能對單個對象做單點寫入,其他節(jié)點備份,無法做多點寫入。但對于不同的對象,其實可以分庫來實現(xiàn)分布式。
  • 當(dāng)沒有發(fā)生分區(qū)現(xiàn)象時,也就是不用考慮P時,上述限制就不存在,此時應(yīng)該考慮如何保障CA。
  • 當(dāng)發(fā)生分區(qū)后,犧牲CAP的其中一個并不代表什么都不用做,而是應(yīng)該為分區(qū)后的恢復(fù)CA做準(zhǔn)備,比如記錄分區(qū)期間的日志以供恢復(fù)時使用。

伴隨CAP的一個退而求其次,也更現(xiàn)實的追求,是BASE理論,即基本可用,保障核心業(yè)務(wù)的可用性;軟狀態(tài),允許系統(tǒng)存在數(shù)據(jù)不一致的中間狀態(tài);最終一致性,一段時間后系統(tǒng)應(yīng)該達(dá)到一致。

FMEA分析法:

要保障高可用,怎么下手呢?俗話說知己知彼才能有的放矢,因此做高可用的前提是了解系統(tǒng)存在怎樣的風(fēng)險,并且還要識別出風(fēng)險的優(yōu)先級,先治理更可能發(fā)生的、影響更大的風(fēng)險。說得簡單,到底怎么做?業(yè)界其實已經(jīng)提供了排查系統(tǒng)風(fēng)險的基本方法論,即FMEA(Failure mode and effects analysis)——故障模式與影響分析。

FMEA的基本思路是,面對初始的架構(gòu)設(shè)計圖,考慮假設(shè)其中某個部件發(fā)生故障,對系統(tǒng)會造成什么影響,進(jìn)而判斷架構(gòu)是否需要優(yōu)化。

具體來說,需要畫一張表,按照如下步驟逐個列出:

  • 功能點:列出業(yè)務(wù)流程中的每個功能點。
  • 故障模式:量化描述該功能可能發(fā)生怎樣的故障,比如mysql響應(yīng)時間超過3秒。
  • 故障影響:量化描述該每個故障可能導(dǎo)致的影響,但不用非常精確,比如20%用戶無法登錄。
  • 嚴(yán)重程度:設(shè)定標(biāo)準(zhǔn),給每個影響的嚴(yán)重程度打分。
  • 故障原因:對于每個故障,考慮有哪些原因?qū)е略摴收稀?/li>
  • 故障概率:對于每個原因,考慮其發(fā)生的概率,不用精確,分檔打分即可。
  • 風(fēng)險程度:=嚴(yán)重程度 * 故障概率,據(jù)此就可以算出風(fēng)險的處理優(yōu)先級了,肯定是程度分?jǐn)?shù)越高的越應(yīng)該優(yōu)先解決。
  • 已有措施、解決措施、后續(xù)規(guī)劃:用于梳理現(xiàn)狀,思考未來的改進(jìn)方案等。

基于上面這套方法論,可以有效地對系統(tǒng)的風(fēng)險進(jìn)行梳理,找出需要優(yōu)先解決的風(fēng)險點,從而提高系統(tǒng)的可用性。

除了FMEA,其實還有一種應(yīng)用更廣泛的風(fēng)險分析和治理的理論,即BCP——業(yè)務(wù)連續(xù)性計劃,它是一套基于業(yè)務(wù)規(guī)律的規(guī)章流程,保障業(yè)務(wù)或組織在面對突發(fā)狀況時其關(guān)鍵業(yè)務(wù)功能可以持續(xù)不中斷。

相比FMEA,BCP除了評估風(fēng)險及重要程度,還要求詳細(xì)地描述應(yīng)對方案、殘余風(fēng)險、災(zāi)備恢復(fù)方案,并要求進(jìn)行相應(yīng)故障的培訓(xùn)和演習(xí)安排,盡最大努力保障業(yè)務(wù)連續(xù)性。

知道風(fēng)險在哪,優(yōu)先治理何種風(fēng)險之后,就可以著手優(yōu)化架構(gòu)。和高性能架構(gòu)模式一樣,高可用架構(gòu)也可以從存儲和計算兩個方面來分析。

3.2、存儲高可用

存儲高可用的本質(zhì)都是通過將數(shù)據(jù)復(fù)制到多個存儲設(shè)備,通過數(shù)據(jù)冗余的方式來提高可用性。

雙機(jī)架構(gòu):

讓我們先從簡單的增加一臺機(jī)器開始,即雙機(jī)架構(gòu)。

當(dāng)機(jī)器變成兩臺后,根據(jù)兩臺機(jī)器擔(dān)任的角色不同,就會分成不同的策略,比如主備、主從、主主。

主備復(fù)制的架構(gòu)是指一臺機(jī)器作為客戶端訪問的主機(jī),另一臺機(jī)器純粹作為冗余備份用,當(dāng)主機(jī)沒有故障時,備機(jī)不會被客戶端訪問到,僅僅需要從主機(jī)同步數(shù)據(jù)。這種策略很簡單,可以應(yīng)對主機(jī)故障情況下的業(yè)務(wù)可用性問題,但在平常無法分擔(dān)主機(jī)的讀寫壓力,有點浪費。

主從復(fù)制的架構(gòu)和主備復(fù)制的差別在于,從機(jī)除了復(fù)制備份數(shù)據(jù),還需要干活,即還需要承擔(dān)一部分的客戶端請求(一般是分擔(dān)讀操作)。當(dāng)主機(jī)故障時,從機(jī)的讀操作不會受到影響,但需要增加讀操作的請求分發(fā)策略,且和主備不同,由于從機(jī)直接提供數(shù)據(jù)讀,如果主從復(fù)制延遲大,數(shù)據(jù)不一致會對業(yè)務(wù)造成更明顯的影響。

對于主備和主從兩種策略,如果主機(jī)故障,都需要讓另一臺機(jī)器變成主機(jī),才能繼續(xù)完整地提供服務(wù),如果全靠人工干預(yù)來切換,會比較滯后和易錯,最好是能夠自動完成切換,這就涉及雙機(jī)切換的策略。

在考慮雙機(jī)切換時,要考慮什么?首先是需要感知機(jī)器的狀態(tài),是兩臺機(jī)器直連傳遞互相的狀態(tài),還是都傳遞給第三方來仲裁?所謂狀態(tài)要包含哪些內(nèi)容才能定義一臺主機(jī)是故障呢?是發(fā)現(xiàn)一次問題就切換還是多觀察一會再切換?切換后如果主機(jī)恢復(fù)了是切換回來還是自動變備機(jī)呢?需不需要人工二次確認(rèn)一下?

這些問題可能都得根據(jù)業(yè)務(wù)的特性來得出答案,此處僅給出三種常見的雙機(jī)切換模式:

  • 互連式:兩臺機(jī)器直接連接傳遞信息,并根據(jù)傳遞的狀態(tài)信息判斷是否要切換主機(jī),如果通道本身發(fā)生故障則無法判斷是否要切換了,可以再增加一個通道構(gòu)成雙通道保障,不過也只是降低同時故障的概率。
  • 中介式:通過第三方中介來收集機(jī)器狀態(tài)并執(zhí)行策略,如果通道發(fā)生斷連,中介可以直接切換其他機(jī)器作為主機(jī),但這要求中介本身是高可用的,已經(jīng)有比較成熟的開源解決方案如zookeeper、keepalived。
  • 模擬式:備機(jī)模擬成客戶端,向主機(jī)發(fā)送業(yè)務(wù)類似的讀寫請求,根據(jù)響應(yīng)情況來判斷主機(jī)的狀態(tài)決定是否要切換主機(jī),這樣可以最真實地感受到客戶端角度下的主機(jī)故障,但和互連式不同,能獲取到的其他機(jī)器信息很少,容易出現(xiàn)判斷偏差。

最后一種雙機(jī)架構(gòu)是主主復(fù)制,和前面兩種只有一主的策略不同,這次兩臺都是主機(jī),客戶端的請求可以達(dá)到任何一臺主機(jī),不存在切換主機(jī)的問題。但這對數(shù)據(jù)的設(shè)計就有了嚴(yán)格的要求,如果存在唯一ID、嚴(yán)格的庫存數(shù)量等數(shù)據(jù),就無法適用,這種策略適合那些偏臨時性、可丟失、可覆蓋的數(shù)據(jù)場景。

數(shù)據(jù)集群:

采用雙機(jī)架構(gòu)的前提是一臺主機(jī)能夠存儲所有的業(yè)務(wù)數(shù)據(jù)并處理所有的業(yè)務(wù)請求,但機(jī)器的存儲和處理能力是有上限的,在大數(shù)據(jù)場景下就需要多臺服務(wù)器來構(gòu)成數(shù)據(jù)集群。

如果是因為處理能力達(dá)到瓶頸,此時可以增加從機(jī)幫主機(jī)分擔(dān)壓力,即一主多從,稱為數(shù)據(jù)集中集群。這種集群方式需要任務(wù)分配算法將請求分散到不同機(jī)器上去,主要的問題在于數(shù)據(jù)需要復(fù)制到多臺從機(jī),數(shù)據(jù)的一致性保障會比一主一從更為復(fù)雜。且當(dāng)主機(jī)故障時,多臺從機(jī)協(xié)商新主機(jī)的策略也會變得復(fù)雜。這里有開源的zookeeper ZAB算法可以直接參考。

如果是因為存儲量級達(dá)到瓶頸,此時可以將數(shù)據(jù)分散存儲到不同服務(wù)器,每臺服務(wù)器負(fù)責(zé)存儲一部分?jǐn)?shù)據(jù),同時也備份一部分?jǐn)?shù)據(jù),稱為數(shù)據(jù)分散集群。數(shù)據(jù)分散集群同樣需要做負(fù)載均衡,在數(shù)據(jù)分區(qū)的分配上,hadoop采用獨立服務(wù)器負(fù)責(zé)數(shù)據(jù)分區(qū)的分配,ES集群通過選舉一臺服務(wù)器來做數(shù)據(jù)分區(qū)的分配。除了負(fù)載均衡,還需要支持?jǐn)U縮容,此外由于數(shù)據(jù)是分散存儲的,當(dāng)部分服務(wù)器故障時,要能夠?qū)⒐收戏?wù)器的數(shù)據(jù)在其他服務(wù)器上恢復(fù),并把原本分配到故障服務(wù)器的數(shù)據(jù)分配到其他正常的服務(wù)器上,即分區(qū)容錯性。

數(shù)據(jù)分區(qū):

數(shù)據(jù)集群可以在單臺乃至多臺服務(wù)器故障時依然保持業(yè)務(wù)可用,但如果因為地理級災(zāi)難導(dǎo)致整個集群都故障了(斷網(wǎng)、火災(zāi)等),那整個服務(wù)就不可用了。面對這種情況,就需要基于不同地理位置做數(shù)據(jù)分區(qū)。

做不同地理位置的數(shù)據(jù)分區(qū),首先要根據(jù)業(yè)務(wù)特性制定分區(qū)規(guī)則,大多還是按照地理位置提供的服務(wù)去做數(shù)據(jù)分區(qū),比如中國區(qū)主要存儲中國用戶的數(shù)據(jù)。

既然分區(qū)是為了防災(zāi),那么一個分區(qū)肯定不止存儲自身的數(shù)據(jù),還需要做數(shù)據(jù)備份。從數(shù)據(jù)備份的策略來說,主要有三種模式:

  • 集中式:存在一個總備份中心,所有的分區(qū)數(shù)據(jù)都往這個總中心備份,設(shè)計起來簡單,各個分區(qū)間沒有聯(lián)系,不會互相影響,也很容易擴(kuò)展新的分區(qū)。但總中心的成本較高,而且總中心如果出故障,就要全部重新備份。
  • 互備式:每個分區(qū)備份另一個分區(qū)的數(shù)據(jù),可以形成一個備份環(huán),或者按地理位置遠(yuǎn)近來搭對備份,這樣可以直接利用已有的設(shè)備做數(shù)據(jù)備份。但設(shè)計較復(fù)雜,各個分區(qū)間需要聯(lián)系,當(dāng)擴(kuò)展新分區(qū)時,需要修改原有的備份線路。
  • 獨立式:每個分區(qū)配備自己的備份中心,一般設(shè)立在分區(qū)地理位置附近的城市,設(shè)計也簡單,各個分區(qū)間不會影響,擴(kuò)展新分區(qū)也容易。但是成本會很高,而且只能防范城市級的災(zāi)難。

3.3、計算高可用

從存儲高可用的思路可以看出,高可用主要是通過增加機(jī)器冗余來實現(xiàn)備份,對計算高可用來說也是如此。通過增加機(jī)器,分擔(dān)服務(wù)器的壓力,并在單機(jī)發(fā)生故障的時候?qū)⒄埱蠓峙涞狡渌麢C(jī)器來保障業(yè)務(wù)可用性。

因此計算高可用的復(fù)雜性也主要是在多機(jī)器下任務(wù)分配的問題,比如當(dāng)任務(wù)來臨(比如客戶端請求到來)時,如何選擇執(zhí)行任務(wù)的服務(wù)器?如果任務(wù)執(zhí)行失敗,如何重新分配呢?這里又可以回到前文說過的負(fù)載均衡相關(guān)的解法上。

計算服務(wù)器和存儲服務(wù)器在多機(jī)器情況下的架構(gòu)是類似的,也分為主備、主從和集群。

主備架構(gòu)下,備機(jī)僅僅用作冗余,平常不會接收到客戶端請求,當(dāng)主機(jī)故障時,備機(jī)才會升級為主機(jī)提供服務(wù)。備機(jī)分為冷備和溫備。冷備是指備機(jī)只準(zhǔn)備好程序包和配置文件,但實際平常并不會啟動系統(tǒng)。溫備是指備機(jī)的系統(tǒng)是持續(xù)啟動的,只是不對外提供服務(wù),從而可以隨時切換主機(jī)。

主從架構(gòu)下,從機(jī)也要執(zhí)行任務(wù),由任務(wù)分配器按照預(yù)先定義的規(guī)則將任務(wù)分配給主機(jī)和從機(jī)。相比起主備,主從可以發(fā)揮一定的從機(jī)性能,避免成本空費,但任務(wù)的分配就變得復(fù)雜一些。

集群架構(gòu)又分為對稱集群和非對稱集群。

對稱集群也叫負(fù)載均衡集群,其中所有的服務(wù)器都是同等對待的,任務(wù)會均衡地分配到每臺服務(wù)器。此時可以采用隨機(jī)、輪詢、Hash等簡單的分配機(jī)制,如果某臺服務(wù)器故障,不再給他分配任務(wù)即可。

非對稱集群下不同的服務(wù)器有不同的角色,比如分為master和slave。此時任務(wù)分配器需要有一定的規(guī)則將任務(wù)分配給不同角色的服務(wù)器,還需要有選舉策略來在master故障時選擇新的master。這個選舉策略的復(fù)雜度就豐儉由人了。

異地多活:

講存儲高可用已經(jīng)說過數(shù)據(jù)分區(qū),計算高可用也有類似的高可用保障思路,歸納來說,它們都可以根據(jù)需要做異地多活,來提高整體的處理能力,并防范地區(qū)級的災(zāi)難。異地多活中的”異地“,就是指集群部署到不同的地理位置,“活”則強(qiáng)調(diào)集群是隨時能提供服務(wù)的,不同于“備”還需要一個切換過程。

按照規(guī)模,異地多活可以分為同城異區(qū)、跨城異地和跨國異地。顯而易見,不同模式下能夠應(yīng)對的地區(qū)級故障是越來越高的,但同樣的,距離越遠(yuǎn),通信成本與延遲就越高,對通信通道可用性的挑戰(zhàn)也越高。因此跨城異地已經(jīng)不適合對數(shù)據(jù)一致性要求非常高的業(yè)務(wù),而跨國異地往往是用來給不同國家的用戶提供不同服務(wù)的。

由于異地多活需要花費很高的成本,極大地增加系統(tǒng)復(fù)雜度,因此在設(shè)計異地多活架構(gòu)時,可以不用強(qiáng)求為所有業(yè)務(wù)都做異地多活,可以優(yōu)先為核心業(yè)務(wù)實現(xiàn)異地多活。盡量保障絕大部分用戶的異地多活,對于沒能保障的用戶,通過掛公告、事后補償、完善失敗提示等措施進(jìn)行安撫、提升體驗。畢竟要做到100%可用性是不可能的,只能在能接受的成本下盡量逼近,所以當(dāng)可用性達(dá)到一定瓶頸后,補償手段的成本或許更低。

在異地部署的情況下,數(shù)據(jù)一定會冗余存儲,物理上就無法實現(xiàn)絕對的實時同步,且距離越遠(yuǎn)對數(shù)據(jù)一致性的挑戰(zhàn)越大,雖然可以靠減少距離、搭建高速專用網(wǎng)絡(luò)等方式來提高一致性,但也只是提高而已,因此大部分情況下, 只需考慮保障業(yè)務(wù)能接受范圍下的最終一致性即可。

在同步數(shù)據(jù)的時候,可以采用多種方式,比如通過消息隊列同步、利用數(shù)據(jù)庫自帶的同步機(jī)制同步、

通過換機(jī)房重試來解決同步延遲問題、通過session id讓同一數(shù)據(jù)的請求都到同一機(jī)房從而不用同步等。

可見,整個異地多活的設(shè)計步驟首先是對業(yè)務(wù)分級,挑選出核心業(yè)務(wù)做異地多活,然后對需要做異地多活的數(shù)據(jù)進(jìn)行特征分析,考慮數(shù)據(jù)量、唯一性、實時性要求、可丟失性、可恢復(fù)性等,根據(jù)數(shù)據(jù)特性設(shè)計數(shù)據(jù)同步的方案。最后考慮各種異常情況下的處理手段,比如多通道同步、日志記錄恢復(fù)、用戶補償?shù)龋藭r可以借用前文所說的FMEA等方法進(jìn)行分析。

接口級故障:

前面討論的都是較為宏觀的服務(wù)器、分區(qū)級的故障發(fā)生時該怎么辦,實際上在平常的開發(fā)中,還應(yīng)該防微杜漸,從接口粒度的角度,來防范和應(yīng)對接口級的故障。應(yīng)對的核心思路依然是優(yōu)先保障核心業(yè)務(wù)和絕大部分用戶可用。

對于接口級故障,有幾個常用的方法:限流、排隊、降級、熔斷。其中限流和排隊屬于事前防范的措施,而降級和熔斷屬于接口真的故障后的處理手段。

限流的目的在于控制接口的訪問量,避免被高頻訪問沖垮。

從限流維度來說,可以基于請求限流,即限制某個指標(biāo)下、某個時間段內(nèi)的請求數(shù)量,閾值的定義需要基于壓測和線上情況來逐步調(diào)優(yōu)。還可以基于資源限流,比如根據(jù)連接數(shù)、文件句柄、線程數(shù)等,這種維度更適合特殊的業(yè)務(wù)。

實現(xiàn)限流常用的有時間窗算法和桶算法。

時間窗算法分為固定時間窗和滑動時間窗。

固定時間窗通過統(tǒng)計固定時間周期內(nèi)的量級來決定限流,但存在一個臨界點的問題,如果在兩個時間窗的中間發(fā)生超大流量,而在兩個時間窗內(nèi)都各自沒有超出限制,就會出現(xiàn)無法被限流攔截的接口故障。因此滑動時間窗采用了部分重疊的時間統(tǒng)計周期來解決臨界點問題。

桶算法分為漏桶和令牌桶。

漏桶算法是將請求放入桶中,處理單元從桶里拿請求去進(jìn)行處理,如果桶堆滿了就丟棄掉新的請求,可以理解為桶下面有個漏斗將請求往處理單元流動,整個桶的容量是有限的。這種模式下流入的速率取決于請求的頻率,當(dāng)桶內(nèi)有堆積的待處理請求時,流出速率是勻速的。漏桶算法適用于瞬時高并發(fā)的場景(如秒殺),處理可能慢一點,但可以緩存部分請求不丟棄。

令牌桶算法是在桶內(nèi)放令牌,令牌數(shù)是有限的,新的請求需要先到桶里拿到令牌才能被處理,拿不到就會被丟棄。和漏桶勻速流出處理不同,令牌桶還能通過控制放令牌的速率來控制接收新請求的頻率,對于突發(fā)流量,可靠累計的令牌來處理,但是相對的處理速度也會突增。令牌桶算法適用于控制第三方服務(wù)訪問速度的場景,防止壓垮下游。

除了限流,還有一種控制處理速度的方法就是排隊。當(dāng)新請求到來后先加入隊列,出隊端通過固定速度出隊處理請求,避免處理單元壓力過大。隊列也有長度限制,其機(jī)制和漏桶算法差不多。

如果真的事前防范真的被突破了,接口很可能或已經(jīng)發(fā)生了故障,還能做什么呢?

一種手段是熔斷,即當(dāng)處理量達(dá)到閾值,就主動停掉外部接口的訪問能力,這其實也是一種防范措施,對外的表現(xiàn)雖然是接口訪問故障,但系統(tǒng)內(nèi)部得以被保護(hù),不會引起更大的問題,待存量處理被消化完,或者外部請求減弱,或完成擴(kuò)容后,再開放接口。熔斷的設(shè)計主要是閾值,需要按照業(yè)務(wù)特點和統(tǒng)計數(shù)據(jù)制定。

當(dāng)接口故障后(無論是被動還是主動斷開),最好能提供降級策略。降級是丟車保帥,放棄一下非核心業(yè)務(wù),保障核心業(yè)務(wù)可用,或者最低程度能提供故障公告,讓用戶不要反復(fù)嘗試請求來加重問題了。比起手動降級,更好的做法也是自動降級,需要具備檢測和發(fā)現(xiàn)降級時機(jī)的機(jī)制。

4、 可擴(kuò)展架構(gòu)模式

再回顧一遍互聯(lián)網(wǎng)行業(yè)的金科玉律:只有變化才是不變的。在設(shè)計架構(gòu)時,一開始就要抱著業(yè)務(wù)隨時可能變動導(dǎo)致架構(gòu)也要跟著變動的思想準(zhǔn)備去設(shè)計,差別只在于變化的快慢而已。因此在設(shè)計架構(gòu)時一定是要考慮可擴(kuò)展性的。

在思考怎樣才是可擴(kuò)展的時候,先想一想平常開發(fā)中什么情況下會覺得擴(kuò)展性不好?大都是因為系統(tǒng)龐大、耦合嚴(yán)重、牽一發(fā)而動全身。因此對可擴(kuò)展架構(gòu)設(shè)計來說,基本的思想就是拆分。

拆分也有多種指導(dǎo)思想,如果面向業(yè)務(wù)流程來談拆分,就是分層架構(gòu);如果面向系統(tǒng)服務(wù)來談拆分,就是SOA、微服務(wù)架構(gòu);如果面向系統(tǒng)功能來拆分,就是微內(nèi)核架構(gòu)。

分層架構(gòu):

分層架構(gòu)是我們最熟悉的,因為互聯(lián)網(wǎng)業(yè)務(wù)下,已經(jīng)很少有純單機(jī)的服務(wù),因此至少都是C/S架構(gòu)、B/S架構(gòu),也就是至少也分為了客戶端/瀏覽器和后臺服務(wù)器這兩層。如果進(jìn)一步拆分,就會將后臺服務(wù)基于職責(zé)進(jìn)行自頂向下的劃分,比如分為接入層、應(yīng)用層、邏輯層、領(lǐng)域?qū)拥取?/p>

分層的目的當(dāng)然是為了讓各個層次間的服務(wù)減少耦合,方便進(jìn)行各自范疇下的優(yōu)化,因此需要保證各層級間的差異是足夠清晰、邊界足夠明顯的,否則當(dāng)要增加新功能的時候就會不知道該放到哪一層。各個層只處理本層邏輯,隔離關(guān)注點。

額外需注意的是一旦確定了分層,請求就必須層層傳遞,不能跳層,這是為了避免架構(gòu)混亂,增加維護(hù)和擴(kuò)展的復(fù)雜度,比如為了方便直接跨層從接入層調(diào)用領(lǐng)域?qū)硬樵償?shù)據(jù),當(dāng)需要進(jìn)行統(tǒng)一的邏輯處理時,就無法切面處理所有請求了。

SOA架構(gòu):

SOA架構(gòu)更多出現(xiàn)在傳統(tǒng)企業(yè)中,其主要解決的問題是企業(yè)中IT建設(shè)重復(fù)且效率低下,各部門自行接入獨立的IT系統(tǒng),彼此之間架構(gòu)、協(xié)議都不同,為了讓各個系統(tǒng)的服務(wù)能夠協(xié)調(diào)工作,SOA架構(gòu)應(yīng)運而生。

其有三個關(guān)鍵概念:服務(wù)、ESB和松耦合。

服務(wù)是指各個業(yè)務(wù)功能,比如原本各部門原本的系統(tǒng)提供的服務(wù),可大可小。由于各服務(wù)之間無法直接通信,因此需要ESB,即企業(yè)服務(wù)總線進(jìn)行對接,它將不同服務(wù)連接在一起,屏蔽各個服務(wù)的不同接口標(biāo)準(zhǔn),類似計算機(jī)中的總線。松耦合是指各個服務(wù)的依賴需要盡量少,否則某個服務(wù)升級后整個系統(tǒng)無法使用就麻煩了。

這里也可以看出,ESB作為總線,為了對接各個服務(wù),要處理各種不同的協(xié)議,其協(xié)議轉(zhuǎn)換耗費了大量的性能,會成為整個系統(tǒng)的瓶頸。

微服務(wù):

微服務(wù)是近幾年最耳熟能詳?shù)募軜?gòu),其實它和SOA有一些相同之處,比如都是將各個服務(wù)拆分開來提供能力。但是和SOA也有一些本質(zhì)的區(qū)別,微服務(wù)是沒有ESB的,其通信協(xié)議是一致的,因此通信管道僅僅做消息的傳遞,不理解內(nèi)容和格式,也就沒有ESB的問題。而且為了快速交付、迭代,其服務(wù)的粒度會劃分地更細(xì),對自動化部署能力也就要求更高,否則部署成本太大,達(dá)不到輕量快速的目的。

當(dāng)然微服務(wù)雖然很火,但也不是解決所有問題的銀彈,它也會有一些問題存在。如果服務(wù)劃分的太細(xì),那么互相之間的依賴關(guān)系就會變得特別復(fù)雜,服務(wù)數(shù)量、接口量、部署量過多,團(tuán)隊的效率可能大降,如果沒有自動化支撐,交付效率會很低。由于調(diào)用鏈太長(多個服務(wù)),因此性能也會下降,問題定位會更困難,如果沒有服務(wù)治理的能力,管理起來會很混亂,不知道每個服務(wù)的情況如何。

因此如何拆分服務(wù)就成了每個使用微服務(wù)架構(gòu)的團(tuán)隊的重要考量點。這里也提供一些拆分的思路:

  • 三個火槍手原則:考慮每三個人負(fù)責(zé)一個服務(wù),互相可以形成穩(wěn)定的人員備份,討論起來也更容易得出結(jié)論,在此基礎(chǔ)上考慮能負(fù)責(zé)多大的一個服務(wù)。
  • 基于業(yè)務(wù)邏輯拆分:最直觀的就是按邏輯拆分,如果職責(zé)不清,就參考三個火槍手原則確定服務(wù)大小。
  • 基于穩(wěn)定性拆分:按照服務(wù)的穩(wěn)定性分為穩(wěn)定服務(wù)和變動服務(wù),穩(wěn)定服務(wù)粒度可以粗一些,變動服務(wù)粒度可以細(xì)一些,目的是減少變動服務(wù)之間的影響,但總體數(shù)量依然要控制。
  • 基于可靠性拆分:按照可靠性排序,要求高的可以拆細(xì)一些,由前文可知,服務(wù)越簡單,高可用方案就會越簡單,成本也會越低。優(yōu)先保障核心服務(wù)的高可用。
  • 基于性能拆分:類似可靠性,性能要求越高的,拆出來單獨做高性能優(yōu)化,可有效降低成本。

微服務(wù)架構(gòu)如果沒有完善的基礎(chǔ)設(shè)施保障服務(wù)治理,那么也會帶來很多問題,降低效率,因此根據(jù)團(tuán)隊和業(yè)務(wù)的規(guī)模,可以按以下優(yōu)先級進(jìn)行基礎(chǔ)設(shè)施的支持:

  1. 優(yōu)先支持服務(wù)發(fā)現(xiàn)、服務(wù)路由、服務(wù)容錯(重試、流控、隔離),這些是微服務(wù)的基礎(chǔ)。
  2. 接著支持接口框架(統(tǒng)一的協(xié)議格式與規(guī)范)、API網(wǎng)關(guān)(接入鑒權(quán)、權(quán)限控制、傳輸加密、請求路由等),可以提高開發(fā)效率。
  3. 然后支持自動化部署、自動化測試能力,并搭建配置中心,可以提升測試和運維的效率。
  4. 最后支持服務(wù)監(jiān)控、服務(wù)跟蹤、服務(wù)安全(接入安全、數(shù)據(jù)安全、傳輸安全、配置化安全策略等)的能力,可以進(jìn)一步提高運維效率。

微內(nèi)核架構(gòu):

最后說說微內(nèi)核架構(gòu),也叫插件化架構(gòu),顧名思義,是面向功能拆分的,通常包含核心系統(tǒng)和插件模塊。在微內(nèi)核架構(gòu)中,核心系統(tǒng)需要支持插件的管理和鏈接,即如何加載插件,何時加載插件,插件如何新增和操作,插件如何和核心引擎通信等。

舉一個最常見的微內(nèi)核架構(gòu)的例子——規(guī)則引擎,在這個架構(gòu)中,引擎是內(nèi)核,負(fù)責(zé)解析規(guī)則,并將輸入通過規(guī)則處理后得到輸出。而各種規(guī)則則是插件,通常根據(jù)各種業(yè)務(wù)場景進(jìn)行配置,存儲到數(shù)據(jù)庫中。

5、總結(jié)

人們通常把某項互聯(lián)網(wǎng)業(yè)務(wù)的發(fā)展分為四個時期:初創(chuàng)期、發(fā)展期、競爭期和成熟期。

在初創(chuàng)期通常求快,系統(tǒng)能買就買,能用開源就用開源,能用的就是好的,先要活下來;到了發(fā)展期開始堆功能和優(yōu)化,要求能快速實現(xiàn)需求,并有余力對一些系統(tǒng)的問題進(jìn)行優(yōu)化,當(dāng)優(yōu)化到頂?shù)臅r候就需要從架構(gòu)層面來拆分優(yōu)化了;進(jìn)入競爭期后,經(jīng)過發(fā)展期的快速迭代,可能會存在很多重復(fù)造輪子和混亂的交互,此時就需要通過平臺化、服務(wù)化來解決一些公共的問題;最后到達(dá)成熟期后,主要在于補齊短板,優(yōu)化弱項,保障系統(tǒng)的穩(wěn)定。

在整個發(fā)展的過程中,同一個功能的前后要求也是不同的,隨著用戶規(guī)模的增加,性能會越來越難保障,可用性問題的影響也會越來越大,因此復(fù)雜度就來了。

對于架構(gòu)師來說,首要的任務(wù)是從當(dāng)前系統(tǒng)的一大堆紛繁復(fù)雜的問題中識別出真正要通過架構(gòu)重構(gòu)來解決的問題,集中力量快速突破,但整體來說,要徐徐圖之,不要想著用重構(gòu)來一次性解決所有問題。

對項目中的問題做好分類,劃分優(yōu)先級,先易后難,才更容易通過較少的資源占用,較快地得到成果,提高士氣。然后再循序漸進(jìn),每個階段控制在1~3個月,穩(wěn)步推進(jìn)。

當(dāng)然,在這個過程中,免不了和上下游團(tuán)隊溝通協(xié)作,需要注意的是自己的目標(biāo)和其他團(tuán)隊的目標(biāo)可能是不同的,需要對重構(gòu)的價值進(jìn)行換位思考,讓雙方都可以合作共贏,才能借力前進(jìn)。

還是回到開頭的那句話,架構(gòu)設(shè)計的主要目的是為了解決軟件系統(tǒng)復(fù)雜度帶來的問題。首先找到主要矛盾在哪,做到有的放矢,然后再結(jié)合知識、經(jīng)驗進(jìn)行設(shè)計,去解決面前的問題。

祝人人都成為一名合格的架構(gòu)師。


關(guān)注我的公眾號【月亮與二進(jìn)制】,鵝廠程序員的敲碼間隙,也能讀書觀影練劍寫字,分享給你我的世界

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容