置富產(chǎn)業(yè)信托(0778.HK)漲4.7% 總市值135億港元
置富產(chǎn)業(yè)信托(0778 HK)漲4 7%,報6 91港元,總市值135億港元。置富產(chǎn)業(yè)信托宣布,已訂立買賣協(xié)議,以8800萬新加坡元(約5 01億港元)收購新加
把書讀薄之『從0開始學架構(gòu)』
(資料圖片)
小到某個功能的開發(fā)方案,大到整個業(yè)務(wù)的系統(tǒng)設(shè)計,都可以看到架構(gòu)設(shè)計的影子,但是架構(gòu)設(shè)計的目的到底是什么?『從0開始學架構(gòu)』的作者給我們的解答是:架構(gòu)設(shè)計的主要目的是為了解決軟件系統(tǒng)復雜度帶來的問題。
這里其實有兩個重點:一是問題,二是解決。
首先得知道我們要解決的問題在哪里?面前的系統(tǒng)到底有什么復雜度導致的問題?只有知道了問題才能選擇解法,不能拿著錘子找釘子。
當知道了當前面臨的問題后,就要利用前人的智慧和自身的經(jīng)驗,設(shè)計出合理的架構(gòu)方案來解決問題。
因此對整本書的內(nèi)容,分成以下四節(jié),第一節(jié)主要描述我們通常面臨的系統(tǒng)復雜度及問題在哪,后面三節(jié)則是對常見的三個問題下的常用解法進行闡述。
在講解架構(gòu)思想之前,先統(tǒng)一介紹一下基本概念的含義,避免每個人對系統(tǒng)、框架、架構(gòu)這些名詞的理解不一致導致的誤解。下面是作者對每個名詞的定義,其作用域僅限本文范疇,不用糾結(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)后,得到的單元就是“模塊”。劃分模塊的主要目的是職責分離。組件:從物理部署的角度來拆分系統(tǒng)后,得到的單元就是“組件”。劃分組件的主要目的是單元復用??蚣埽菏且徽组_發(fā)規(guī)范,是提供基礎(chǔ)功能的產(chǎn)品。架構(gòu):關(guān)注的是結(jié)構(gòu),是某一套開發(fā)規(guī)范下的具體落地方案,包括各個模塊之間的組合關(guān)系以及它們協(xié)同起來完成功能的運作規(guī)則。由以上定義可見,所謂架構(gòu),是為了解決軟件系統(tǒng)的某個復雜度帶來的具體問題,將模塊和組件以某種方式有機組合,基于某個具體的框架實現(xiàn)后的一種落地方案。
而討論架構(gòu)時,往往只討論到系統(tǒng)與子系統(tǒng)這個頂層的架構(gòu)。
可見,要進行架構(gòu)選型,首先應該知道自己要解決的業(yè)務(wù)和系統(tǒng)復雜點在哪里,是作為秒殺系統(tǒng)有瞬間高并發(fā),還是作為金融科技有極高的數(shù)據(jù)一致性和可用性要求等。
一般來說,系統(tǒng)的復雜度來源有以下幾個方面:
高性能:
如果業(yè)務(wù)的訪問頻率或?qū)崟r性要求較高,則會對系統(tǒng)提出高性能的要求。
如果是單機系統(tǒng),需要利用多進程、多線程技術(shù)。
如果是集群系統(tǒng),則還涉及任務(wù)拆分、分配與調(diào)度,多機器狀態(tài)管理,機器間通信,當單機性能達到瓶頸后,即使繼續(xù)加機器也無法繼續(xù)提升性能,還是要針對單個子任務(wù)進行性能提升。
高可用:
如果業(yè)務(wù)的可用性要求較高,也會帶來高可用方面的復雜度。高可用又分為計算高可用和存儲高可用。
針對計算高可用,可以采用主備(冷備、溫備、熱備)、多主的方式來冗余計算能力,但會增加成本、可維護性方面的復雜度。
針對存儲高可用,同樣是增加機器來冗余,但這也會帶來多機器導致的數(shù)據(jù)不一致問題,如遇到延遲、中斷、故障等情況。難點在于怎么減少數(shù)據(jù)不一致對業(yè)務(wù)的影響。
既然主要解決思路是增加機器來做冗余,那么就涉及到了狀態(tài)決策的問題。即如果判斷當前主機的狀態(tài)是正常還是異常,以及異常了要如何采取行動(比如切換哪臺做主機)。
對主機狀態(tài)的判斷,多采用機器信息采集或請求響應情況分析等手段,但又會產(chǎn)生采集信息這一條通信鏈路本身是否正常的問題,下文會具體展開討論。事實上,狀態(tài)決策本質(zhì)上不可能做到完全正確。
而對于決策方式,有以下幾種方式:
獨裁式:存在一個獨立的決策主體來收集信息并決定主機,這樣的策略不會混亂,但這個主體本身存在單點問題。協(xié)商式:兩臺備機通過事先指定的規(guī)則來協(xié)商決策出主機,規(guī)則雖然簡單方便,但是如果兩臺備機之間的協(xié)商鏈路中斷了,決策起來就會很困難,比如有網(wǎng)絡(luò)延遲且機器未故障、網(wǎng)絡(luò)中斷且機器未故障、網(wǎng)絡(luò)中斷其機器已故障,多種情況需要處理。民主式:如果有多臺備機,可以使用選舉算法來投票出主機,比如Paxos就是一種選舉算法,這種算法大多數(shù)都采取多數(shù)取勝的策略,算法本身較為復雜,且如果像協(xié)商式一樣出現(xiàn)連接中斷,就會腦裂,不同部分會各自決策出不同結(jié)果,需要規(guī)避。可擴展性:
眾所周知在互聯(lián)網(wǎng)行業(yè)只有變化才是永遠不變的,而開發(fā)一個系統(tǒng)基本都不是一蹴而就的,那應該如何為系統(tǒng)的未來可能性進行設(shè)計來保持可擴展性呢?
這里首先要明確的一個觀點就是,在做系統(tǒng)設(shè)計時,既不可能完全不考慮可擴展性,也不可能每個設(shè)計點都考慮可擴展性,前者很明顯,后者則是為了避免舍本逐末,為了擴展而擴展,實際上可能會為不存在的預測花費過多的精力。
那么怎么考慮系統(tǒng)的未來可能性從而做出相應的可擴展性設(shè)計呢?這里作者給出了一個方法:只預測兩年內(nèi)可能的變化,不要試圖預測五年乃至十年的變化。因為對于變化快的行業(yè)來說,預測兩年已經(jīng)足夠遠了,再多就可能計劃趕不上變化。而對變化慢的行業(yè),則預測的意義更是不大。
要應對變化,主要是將變與不變分隔開來。
這里可以針對業(yè)務(wù),提煉變化層和穩(wěn)定層,通過變化層將變化隔離。比如通過一個DAO服務(wù)來對接各種變化的存儲載體,但是上層穩(wěn)定的邏輯不用知曉當前采用何種存儲,只需按照固定的接口訪問DAO即可獲取數(shù)據(jù)。
也可以將一些實現(xiàn)細節(jié)剝離開來,提煉出抽象層,僅在實現(xiàn)層去封裝變化。比如面對運營上經(jīng)常變化的業(yè)務(wù)規(guī)則,可以提煉出一個規(guī)則引擎來實現(xiàn)核心的抽象邏輯,而具體的規(guī)則實現(xiàn)則可以按需增加。
如果是面對一個舊系統(tǒng)的維護,接到了新的重復性需求,而舊系統(tǒng)并不支持較好的可擴展性,這時是否需要花費時間精力去重構(gòu)呢?作者也提出了《重構(gòu)》一書中提到的原則:事不過三,三則重構(gòu)。
簡而言之,不要一開始就考慮復雜的做法去滿足可擴展性,而是等到第三次遇到類似的實現(xiàn)時再來重構(gòu),重構(gòu)的時候采取上述說的隔離或者封裝的方案。這一原則對
這一原則對新系統(tǒng)開發(fā)也是適用的。總而言之就是,不要為難以預測的未來去過度設(shè)計,為明確的未來保留適量的可擴展性即可。
低成本:
上面說的高性能、高可用都需要增加機器,帶來的是成本的增加,而很多時候研發(fā)的預算是有限的。換句話說,低成本往往并不是架構(gòu)設(shè)計的首要目標,而是設(shè)計架構(gòu)時的約束限制。
那如何在有限的成本下滿足復雜性要求呢?往往只有“創(chuàng)新”才能達到低成本的目標。舉幾個例子:
NoSQL的出現(xiàn)是為解決關(guān)系型數(shù)據(jù)庫應對高并發(fā)的問題。全文搜索引擎的出現(xiàn)是為解決數(shù)據(jù)庫like搜索效率的問題。Hadoop的出現(xiàn)是為解決文件系統(tǒng)無法應對海量數(shù)據(jù)存儲與計算的問題。Facebook的HipHop PHP和HHVM的出現(xiàn)是為解決PHP運行低效問題。新浪微博引入SSD Cache做L2緩存是為解決Redis高成本、容量小、穿透DB的問題。Linkedin引入Kafka是為解決海量事件問題。上述案例都是為了在不顯著增加成本的前提下,實現(xiàn)系統(tǒng)的目標。
這里還要說明的是,創(chuàng)造新技術(shù)的復雜度本身就是很高的,因此一般中小公司基本都是靠引入現(xiàn)有的成熟新技術(shù)來達到低成本的目標;而大公司才更有可能自己去創(chuàng)造新的技術(shù)來達到低成本的目標,因為大公司才有足夠的資源、技術(shù)和時間去創(chuàng)造新技術(shù)。
安全:
安全是一個研發(fā)人員很熟悉的目標,從整體來說,安全包含兩方面:功能安全和架構(gòu)安全。
功能安全是為了“防小偷”,即避免系統(tǒng)因安全漏洞而被竊取數(shù)據(jù),如SQL注入。常見的安全漏洞已經(jīng)有很多框架支持,所以更建議利用現(xiàn)有框架的安全能力,來避免重復開發(fā),也避免因自身考慮不夠全面而遺漏。在此基礎(chǔ)上,仍需持續(xù)攻防來完善自身的安全。
架構(gòu)安全是為了“防強盜“,即避免系統(tǒng)被暴力攻擊導致系統(tǒng)故障,比如DDOS攻擊。這里一方面只能通過防火墻集運營商或云服務(wù)商的大帶寬和流量清洗的能力進行防范,另一方面也需要做好攻擊發(fā)現(xiàn)與干預、恢復的能力。
規(guī)模:
架構(gòu)師在宣講時往往會先說自己任職和設(shè)計過的大型公司的架構(gòu),這是因為當系統(tǒng)的規(guī)模達到一定程度后,復雜度會發(fā)生質(zhì)的變化,即所謂量變引起質(zhì)變。
這個量,體現(xiàn)在訪問量、功能數(shù)量及數(shù)據(jù)量上。
訪問量映射到對高性能的要求。功能數(shù)量需要視具體業(yè)務(wù)會帶來不同的復雜度。而數(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)過上面的分析可以看到,復雜度來源很多,想要一一應對,似乎會得到一個復雜無比的架構(gòu),但對于架構(gòu)設(shè)計來說,其實剛開始設(shè)計時越簡單越好,只要能解決問題,就可以從簡單開始再慢慢去演化,對應的是下面三條原則:
合適原則:不需要一開始就挑選業(yè)界領(lǐng)先的架構(gòu),它也許優(yōu)秀,但可能不那么適合自己,比如有很多目前用不到的能力或者大大超出訴求從而增加很多成本。其實更需要考慮的是合理地將資源整合在一起發(fā)揮出最大功效,并能夠快速落地。簡單原則:有時候為了顯示出自身的能力,往往會在一開始就將系統(tǒng)設(shè)計得非常復雜,復雜可能代表著先進,但更可能代表著“問題”,組件越多,就越可能出故障,越可能影響關(guān)聯(lián)著的組件,定位問題也更加困難。其實只要能夠解決訴求即可。演化原則:不要妄想一步到位,沒有人可以準確預測未來所有發(fā)展,軟件不像建筑,變化才是主題。架構(gòu)的設(shè)計應該先滿足業(yè)務(wù)需求,適當?shù)念A留擴展性,然后在未來的業(yè)務(wù)發(fā)展中再不斷地迭代,保留有限的設(shè)計,修復缺陷,改正錯誤,去除無用部分。這也是重構(gòu)、重寫的價值所在。即使是QQ、淘寶這種如今已經(jīng)非常復雜的系統(tǒng),剛開始時也只是一個簡單的系統(tǒng),甚至淘寶都是直接買來的系統(tǒng),隨著業(yè)務(wù)發(fā)展也只是先加服務(wù)器、引入一些組件解決性能問題,直到達到瓶頸才去重構(gòu)重寫,重新在新的復雜度要求下設(shè)計新的架構(gòu)。
明確了設(shè)計原則后,當面對一個具體的業(yè)務(wù),可以按照如下步驟進行架構(gòu)設(shè)計:
識別復雜度:無論是新設(shè)計一個系統(tǒng)還是接手一個混亂的系統(tǒng),第一步都是先將主要的復雜度問題列出來,然后根據(jù)業(yè)務(wù)、技術(shù)、團隊等綜合情況進行排序,優(yōu)先解決當前面臨的最主要的復雜度問題。復雜度的主要來源上文已經(jīng)說過,可以按照經(jīng)驗或者排查法進行分析。方案對比:先看看業(yè)界是否有類似的業(yè)務(wù),了解他們是怎么解決問題的,然后提出3~5個備選方案,不要只考慮做一個最優(yōu)秀的方案,一個人的認知范圍常常是有限的,逼自己多思考幾個方案可以有效規(guī)避因為思維狹隘導致的局限性,當然也不要過多,不用給出非常詳細的方案,太消耗精力。備選方案的差異要比較明顯,才有擴寬思路和對比的價值。設(shè)計詳細方案:當多個方案對比得出最終選擇后,就可以對目標方案進行詳細的設(shè)計,關(guān)鍵細節(jié)需要比較深入,如果方案本身很復雜,也可以采取分步驟、分階段、分系統(tǒng)的實現(xiàn)方式來降低實現(xiàn)復雜度。當方案非常龐大的時候,可以匯集一個團隊的智慧和經(jīng)驗來共同設(shè)計,防止因架構(gòu)師的思維盲區(qū)導致問題。互聯(lián)網(wǎng)業(yè)務(wù)大多是數(shù)據(jù)密集型的業(yè)務(wù),其對性能的壓力也常常來自于海量用戶對數(shù)據(jù)的高頻讀寫壓力上,因此解決高性能問題,首先要解決數(shù)據(jù)讀寫的存儲高性能問題。
讀寫分離:
在大多數(shù)業(yè)務(wù)中,用戶查詢和修改數(shù)據(jù)的頻率是不同的,甚至是差別很大的,大部分情況下都是讀多寫少的,因此可以將對數(shù)據(jù)的讀和寫操作分開對待,對壓力更大的讀操作提供額外的機器分擔壓力,這就是讀寫分離。
讀寫分離的基本實現(xiàn)是搭建數(shù)據(jù)庫的主從集群,根據(jù)需要提供一主一從或一主多從。
注意是主從不是主備,從和備的差別在于從機是要干活的。
通常在讀多寫少的情況下,主機負責讀寫操作,從機只負責讀操作,負責幫主機分擔讀操作的壓力。而數(shù)據(jù)會通過復制機制(如全同步、半同步、異步)同步到從機,每臺服務(wù)器都有所有業(yè)務(wù)數(shù)據(jù)。
既然有數(shù)據(jù)的同步,就一定存在復制延遲導致的從機數(shù)據(jù)不一致問題,針對這個問題有幾種常見的解法,如:
寫操作后同一用戶一段時間內(nèi)的讀操作都發(fā)給主機,避免數(shù)據(jù)還沒同步到從機,但這個邏輯容易遺漏。讀從機失敗后再讀一次主機,該方法只能解決新數(shù)據(jù)未同步的問題,無法解決舊數(shù)據(jù)修改的問題(不會讀取失?。?,且二次讀取主機會給主機帶來負擔,容易被針對性攻擊。關(guān)鍵讀寫操作全部走主機,從機僅負責非關(guān)鍵鏈路的讀,該方法是基于保障關(guān)鍵業(yè)務(wù)的思路。除了數(shù)據(jù)同步的問題之外,只要涉及主從機同時支持業(yè)務(wù)訪問的,就一定需要制定請求分配的機制。上面說的幾個問題解法也涉及了一些分配機制的細節(jié)。具體到分配機制的實現(xiàn)來說,有兩種思路:
程序代碼封裝:實現(xiàn)簡單,可對業(yè)務(wù)定制化,但每個語言都要自己實現(xiàn)一次,且很難做到同步修改,因此適合小團隊。中間件封裝:獨立出一套系統(tǒng)管理讀寫的分配,對業(yè)務(wù)透明,兼容SQL協(xié)議,業(yè)務(wù)服務(wù)器就無需做額外修改適配。需要支持多語言、完整的SQL語法,涉及很多細節(jié),容易出BUG,且本身是個單點,需要特別保障性能和可用性,因此適合大公司。分庫分表:
除了高頻訪問的壓力,當數(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ù)留在原庫中。
分庫一方面可以緩解訪問和存儲的壓力,另一方面也可以增加抗風險能力,當一個庫出問題后,另一個庫中的數(shù)據(jù)并不會受到影響,而且還能分開管理權(quán)限。
但分庫也會帶來一些問題,原本同一個庫中的不同表可以方便地進行聯(lián)表查詢,分庫后則會變得很復雜。由于數(shù)據(jù)在不同的庫中,當要操作兩個庫中的數(shù)據(jù)時,無法使用事務(wù)操作,一致性也變得更難以保障。而且當增加備庫來保障可用性的時候,成本是成倍增加的。
基于以上問題,初創(chuàng)的業(yè)務(wù)并不建議在一開始就做這種拆分,會增加很多開發(fā)時的成本和復雜度,拖慢業(yè)務(wù)的節(jié)奏。
再說分表,所謂分表,就是將原本存儲在一張表里的數(shù)據(jù),按照不同的維度,拆分成多張表來存儲。
按照訴求與業(yè)務(wù)的特性不同,可以采用垂直分表或水平分表的方式。
垂直分表相當于垂直地給原表切了一刀,把不同的字段拆分到不同的子表中,這樣拆分后,原本訪問一張表可以獲取的所有字段,現(xiàn)在則需要訪問不同的表獲取。
垂直分表適合將表中某些不常用又占了大量空間的列(字段)拆分出去,可以提升訪問常用字段的性能。
但相應的,當真的需要的字段處于不同表中時,或者要新增記錄存儲所有字段數(shù)據(jù)時,要操作的表變多了。
水平分表相當于橫著給原表切了一刀,那么原表中的記錄會被分散存儲到不同的子表中,但是每張子表的字段都是全部字段。
水平分表適合表的量級很大以至影響訪問性能的場景,何時該拆分并沒有絕對的指標,一般記錄數(shù)超過千萬時就需要警覺了。
不同于垂直分表依然能訪問到所有記錄,水平分表后無法再在一張表中訪問所有數(shù)據(jù)了,因此很多查詢操作會受到影響,比如join操作就需要多次查詢后合并結(jié)果,count操作也需要計算多表的結(jié)果后相加,如果經(jīng)常用到count的總數(shù),可以額外維護一個總數(shù)表去更新,但也會帶來數(shù)據(jù)一致性的問題。
值得特別提出的是范圍查詢,原本的一張表可以通過范圍查詢到的數(shù)據(jù),分表后也需要多次查詢后合并數(shù)據(jù),如果是業(yè)務(wù)經(jīng)常用到的范圍查詢,那建議干脆就按照這種方式來分表,這也是分表的路由方式之一:范圍路由。
所謂路由方式是指:分表后當新插入記錄時,如何判斷該往哪張表插入。常用的插入方式有以下三種:
范圍路由:按照時間范圍、ID范圍或者其他業(yè)務(wù)常用范圍字段路由。這種方式在擴充新的表時比較方便,直接加表給新范圍的數(shù)據(jù)插入即可,但是數(shù)量和冷熱分布可能是不均勻的。Hash路由:根據(jù)Hash運算來路由新記錄插入的表,這種方式需要提前就規(guī)劃好分多少張表,才能決定Hash運算方式,但表數(shù)量其實很難預估,導致未來需要擴充新表時很麻煩,但數(shù)據(jù)在不同表中的分布是比較均勻的。配置路由:新增一個路由表來記錄數(shù)據(jù)id和表id的映射,按照自定義的方式隨時修改映射規(guī)則,設(shè)計簡單,擴充新表也很方便,但每次操作表都需要額外操作一次路由表,其本身也成為了單點瓶頸。無論是垂直分表還是水平分表,單表切分為多表后,新的表即使在同一個數(shù)據(jù)庫服務(wù)器中,也可能帶來可觀的性能提升,如果性能能夠滿足業(yè)務(wù)要求,可以不拆分到多臺數(shù)據(jù)庫服務(wù)器,畢竟分庫也會引入很多復雜性的問題;如果單表拆分為多表后,單臺服務(wù)器依然無法滿足性能要求,那就不得不再次進行業(yè)務(wù)分庫的設(shè)計了。
NoSQL數(shù)據(jù)庫:
上面發(fā)分庫分表討論的都是關(guān)系型數(shù)據(jù)庫的優(yōu)化方案,但關(guān)系型數(shù)據(jù)庫也有其無法規(guī)避的缺點,比如無法直接存儲某種結(jié)構(gòu)化的數(shù)據(jù)、擴展表結(jié)構(gòu)時會鎖表影響線上性能、大數(shù)據(jù)場景下I/O較高、全文搜索的功能比較弱等。
基于這些缺點,也有很多新的數(shù)據(jù)庫框架被創(chuàng)造出來,解決其某方面的問題。
比如以Redis為代表的的KV存儲,可以解決無法存儲結(jié)構(gòu)化數(shù)據(jù)的問題;以MongoDB為代表的的文檔數(shù)據(jù)庫可以解決擴展表結(jié)構(gòu)被強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)存中,當業(yè)務(wù)請求來查詢的時候直接從內(nèi)存中拿出來,不用重新去數(shù)據(jù)庫中按條件查詢,也就省去了大量的磁盤IO時間。
一般來說緩存都是通過Key-Value的方式存儲在內(nèi)存中,根據(jù)存儲的位置,分為單機緩存和集中式緩存。單機緩存就是存在自身服務(wù)器所在的機器上,那么勢必會有不同機器數(shù)據(jù)可能不一致,或者重復緩存的問題,要解決可以使用查詢內(nèi)容做路由來保障同一記錄始終到同一臺機器上查詢緩存。集中式緩存則是所有服務(wù)器都去一個地方查緩存,會增加一些調(diào)用時間。
緩存可以提升性能是很好理解的,但緩存同樣有著他的問題需要應對或規(guī)避。數(shù)據(jù)時效性是最容易想到的問題,但也可以靠同時更新緩存的策略來保障數(shù)據(jù)的時效性,除此之外還有其他幾個常見的問題。
如果某條數(shù)據(jù)不存在,緩存中勢必查不到對應的KEY,從而就會請求數(shù)據(jù)庫確認是否有新增加這條數(shù)據(jù),如果始終沒有這條數(shù)據(jù),而客戶端又反復頻繁地查詢這條數(shù)據(jù),就會變相地對數(shù)據(jù)庫造成很大的壓力,換句話說,緩存失去了保護作用,請求穿透到了數(shù)據(jù)庫,這稱為緩存穿透。
應對緩存穿透,最好的手段就是把“空值”這一情況也緩存下來,當客戶端下次再查詢時,發(fā)現(xiàn)緩存中說明了該數(shù)據(jù)是空值,則不會再問詢數(shù)據(jù)庫。但也要注意如果真的有對應數(shù)據(jù)寫入了數(shù)據(jù)庫,應當能及時清除”空值“緩存。
為了保障緩存的數(shù)據(jù)及時更新,常常都會根據(jù)業(yè)務(wù)特性設(shè)置一個緩存過期時間,在緩存過期后,到再次生成期間,如果出現(xiàn)大量的查詢,會導致請求都傳遞到數(shù)據(jù)庫,而且會多次重復生成緩存,甚至可能拖垮整個系統(tǒng),這就叫緩存雪崩,和緩存穿透的區(qū)別在于,穿透是面對空值的情況,而雪崩是由于緩存重新生成的間隔期大量請求產(chǎn)生的連鎖效應。
既然是緩存更新時重復生成所導致的問題,那么一種解法就是在緩存重新生成前給這個KEY加鎖,加鎖期間出現(xiàn)的請求都等待或返回默認值,而不去都嘗試重新生成緩存。
另一種方法是干脆不要由客戶端請求來觸發(fā)緩存更新,而是由后臺腳本統(tǒng)一更新,同樣可以規(guī)避重復請求導致的重復生成。但是這就失去了只緩存熱點數(shù)據(jù)的能力,如果緩存因空間問題被清除了,也會因為后臺沒及時更新導致查不到緩存數(shù)據(jù),這就會要求更復雜的后臺更新策略,比如主動查詢緩存有效性、緩存被刪后通知后臺主動更新等。
雖說在有限的內(nèi)存空間內(nèi)最好緩存熱點數(shù)據(jù),但如果數(shù)據(jù)過熱,比如微博的超級熱搜,也會導致緩存服務(wù)器壓力過大而崩潰,稱之為緩存熱點問題。
可以復制多份緩存副本,來分散緩存服務(wù)器的單機壓力,畢竟堆機器是最簡單有效。此處也要注意,多個緩存副本不要設(shè)置相同的緩存過期時間,否則多處緩存同時過期,并同時更新,也容易引起緩存雪崩,應該設(shè)置一個時間范圍內(nèi)的隨機值來更新緩存。
講完存儲高性能,再講計算高性能,計算性能的優(yōu)化可以先從單機性能優(yōu)化開始,多進程、多線程、IO多路復用、異步IO等都存在很多可以優(yōu)化的地方,但基本系統(tǒng)或框架已經(jīng)提供了基本的優(yōu)化能力,只需使用即可。
負載均衡:
如果單機的性能優(yōu)化已經(jīng)到了瓶頸,無法應對業(yè)務(wù)的增長,就會開始增加服務(wù)器,構(gòu)建集群。對于計算來說,每一臺服務(wù)器接到同樣的輸入,都應該返回同樣的輸出,當服務(wù)器從單臺變成多臺之后,就會面臨請求來了要由哪一臺服務(wù)器處理的問題,我們當然希望由當前比較空閑的服務(wù)器去處理新的請求,這里對請求任務(wù)的處理分配問題,就叫負載均衡。
負載均衡的策略,從分類上來說,可以分為三類:
DNS負載均衡:通過DNS解析,來實現(xiàn)地理級別的均衡,其成本低,分配策略很簡單,可以就近訪問來提升訪問速度,但DNS的緩存時間長,由于更新不及時所以無法快速調(diào)整,且控制權(quán)在各域名商下,且無法根據(jù)后端服務(wù)器的狀態(tài)來決定分配策略。硬件負載均衡:直接通過硬件設(shè)備來實現(xiàn)負載均衡,類似路由器路由,功能和性能都很強大,可以做到百萬并發(fā),也很穩(wěn)定,支持安全防護能力,但是同樣無法根據(jù)后端服務(wù)器狀態(tài)進行策略調(diào)整,且價格昂貴。軟件負載均衡:通過軟件邏輯實現(xiàn),比如nginx,比較靈活,成本低,但是性能一般,功能也不如硬件強大。一般來說,DNS 負載均衡用于實現(xiàn)地理級別的負載均衡;硬件負載均衡用于實現(xiàn)集群級別的負載均衡;軟件負載均衡用于實現(xiàn)機器級別的負載均衡。
所以部署起來可以按照這三層去部署,第一層通過DNS將請求分發(fā)到北京、上海、深圳的機房;第二層通過硬件負載均衡將請求分發(fā)到當?shù)厝齻€集群中的一個;第三層通過軟件策略將請求分發(fā)到具體的某臺服務(wù)器去響應業(yè)務(wù)。
就負載均衡算法來說,多是我們很熟悉的算法,如輪詢、加權(quán)輪詢、負載最低優(yōu)先、性能最優(yōu)優(yōu)先、Hash分配等,各有特點,按需采用即可。
CAP與BASE:
在說高可用之前,先來說說CAP理論,即:
在一個分布式系統(tǒng)(指互相連接并共享數(shù)據(jù)的節(jié)點的集合)中,當涉及讀寫操作時,只能保證一致性(Consistence)、可用性(Availability)、分區(qū)容錯性(Partition Tolerance)三者中的兩個,另外一個必須被犧牲。
大家可能都知道CAP定理是什么,但大家可能不知道,CAP定理的作者(Seth Gilbert & Nancy Lynch)其實并沒有詳細解釋CAP三個單詞的具體含義,目前大家熟悉的解釋其實是另一個人(Robert Greiner)給出的。而且他還給出了兩版有所差異的解釋。
第二版解釋算是對第一版解釋的加強,他要加強的點主要是:
CAP描述的分布式系統(tǒng),是互相連結(jié)并共享數(shù)據(jù)的節(jié)點的集合。因為其實并不是所有的分布式系統(tǒng)都會互連和共享數(shù)據(jù)。CAP理論是在涉及讀寫操作的場景下的理論,而不是分布式系統(tǒng)的所有功能。一致性只需要保障客戶端讀操作能讀到最新的寫操作結(jié)果,并不要求時時刻刻分布式系統(tǒng)的數(shù)據(jù)都是一致的,這是不現(xiàn)實的,只要保障客戶讀到的一致即可。可用性要求非故障的節(jié)點在合理的時間內(nèi)能返回合理的響應,所謂合理是指非錯誤、非超時,即使數(shù)據(jù)不是最新的數(shù)據(jù),也是合理的“舊數(shù)據(jù)”,是符合可用性的。分區(qū)容錯性要求網(wǎng)絡(luò)分區(qū)后系統(tǒng)能繼續(xù)履行職責,不僅僅要求系統(tǒng)不宕機,還要求能發(fā)揮作用,能處理業(yè)務(wù)邏輯。比如接口直接返回錯誤其實也代表系統(tǒng)在運行,但卻沒有履行職責。在分布式系統(tǒng)下,P(分區(qū)容忍)是必須選擇的,否則當分區(qū)后系統(tǒng)無法履行職責時,為了保障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è)務(wù)),其實是可以按照業(yè)務(wù)特性采取不同的CAP策略的。CAP實際忽略了網(wǎng)絡(luò)延遲,也就是允許數(shù)據(jù)復制過程中的短時間不一致,如果某些業(yè)務(wù)比如金融業(yè)務(wù)無法容忍這一點,那就只能對單個對象做單點寫入,其他節(jié)點備份,無法做多點寫入。但對于不同的對象,其實可以分庫來實現(xiàn)分布式。當沒有發(fā)生分區(qū)現(xiàn)象時,也就是不用考慮P時,上述限制就不存在,此時應該考慮如何保障CA。當發(fā)生分區(qū)后,犧牲CAP的其中一個并不代表什么都不用做,而是應該為分區(qū)后的恢復CA做準備,比如記錄分區(qū)期間的日志以供恢復時使用。伴隨CAP的一個退而求其次,也更現(xiàn)實的追求,是BASE理論,即基本可用,保障核心業(yè)務(wù)的可用性;軟狀態(tài),允許系統(tǒng)存在數(shù)據(jù)不一致的中間狀態(tài);最終一致性,一段時間后系統(tǒng)應該達到一致。
FMEA分析法:
要保障高可用,怎么下手呢?俗話說知己知彼才能有的放矢,因此做高可用的前提是了解系統(tǒng)存在怎樣的風險,并且還要識別出風險的優(yōu)先級,先治理更可能發(fā)生的、影響更大的風險。說得簡單,到底怎么做?業(yè)界其實已經(jīng)提供了排查系統(tǒng)風險的基本方法論,即FMEA(Failure mode and effects analysis)——故障模式與影響分析。
FMEA的基本思路是,面對初始的架構(gòu)設(shè)計圖,考慮假設(shè)其中某個部件發(fā)生故障,對系統(tǒng)會造成什么影響,進而判斷架構(gòu)是否需要優(yōu)化。
具體來說,需要畫一張表,按照如下步驟逐個列出:
功能點:列出業(yè)務(wù)流程中的每個功能點。故障模式:量化描述該功能可能發(fā)生怎樣的故障,比如mysql響應時間超過3秒。故障影響:量化描述該每個故障可能導致的影響,但不用非常精確,比如20%用戶無法登錄。嚴重程度:設(shè)定標準,給每個影響的嚴重程度打分。故障原因:對于每個故障,考慮有哪些原因?qū)е略摴收稀9收细怕剩簩τ诿總€原因,考慮其發(fā)生的概率,不用精確,分檔打分即可。風險程度:=嚴重程度 * 故障概率,據(jù)此就可以算出風險的處理優(yōu)先級了,肯定是程度分數(shù)越高的越應該優(yōu)先解決。已有措施、解決措施、后續(xù)規(guī)劃:用于梳理現(xiàn)狀,思考未來的改進方案等。基于上面這套方法論,可以有效地對系統(tǒng)的風險進行梳理,找出需要優(yōu)先解決的風險點,從而提高系統(tǒng)的可用性。
除了FMEA,其實還有一種應用更廣泛的風險分析和治理的理論,即BCP——業(yè)務(wù)連續(xù)性計劃,它是一套基于業(yè)務(wù)規(guī)律的規(guī)章流程,保障業(yè)務(wù)或組織在面對突發(fā)狀況時其關(guān)鍵業(yè)務(wù)功能可以持續(xù)不中斷。
相比FMEA,BCP除了評估風險及重要程度,還要求詳細地描述應對方案、殘余風險、災備恢復方案,并要求進行相應故障的培訓和演習安排,盡最大努力保障業(yè)務(wù)連續(xù)性。
知道風險在哪,優(yōu)先治理何種風險之后,就可以著手優(yōu)化架構(gòu)。和高性能架構(gòu)模式一樣,高可用架構(gòu)也可以從存儲和計算兩個方面來分析。
存儲高可用的本質(zhì)都是通過將數(shù)據(jù)復制到多個存儲設(shè)備,通過數(shù)據(jù)冗余的方式來提高可用性。
雙機架構(gòu):
讓我們先從簡單的增加一臺機器開始,即雙機架構(gòu)。
當機器變成兩臺后,根據(jù)兩臺機器擔任的角色不同,就會分成不同的策略,比如主備、主從、主主。
主備復制的架構(gòu)是指一臺機器作為客戶端訪問的主機,另一臺機器純粹作為冗余備份用,當主機沒有故障時,備機不會被客戶端訪問到,僅僅需要從主機同步數(shù)據(jù)。這種策略很簡單,可以應對主機故障情況下的業(yè)務(wù)可用性問題,但在平常無法分擔主機的讀寫壓力,有點浪費。
主從復制的架構(gòu)和主備復制的差別在于,從機除了復制備份數(shù)據(jù),還需要干活,即還需要承擔一部分的客戶端請求(一般是分擔讀操作)。當主機故障時,從機的讀操作不會受到影響,但需要增加讀操作的請求分發(fā)策略,且和主備不同,由于從機直接提供數(shù)據(jù)讀,如果主從復制延遲大,數(shù)據(jù)不一致會對業(yè)務(wù)造成更明顯的影響。
對于主備和主從兩種策略,如果主機故障,都需要讓另一臺機器變成主機,才能繼續(xù)完整地提供服務(wù),如果全靠人工干預來切換,會比較滯后和易錯,最好是能夠自動完成切換,這就涉及雙機切換的策略。
在考慮雙機切換時,要考慮什么?首先是需要感知機器的狀態(tài),是兩臺機器直連傳遞互相的狀態(tài),還是都傳遞給第三方來仲裁?所謂狀態(tài)要包含哪些內(nèi)容才能定義一臺主機是故障呢?是發(fā)現(xiàn)一次問題就切換還是多觀察一會再切換?切換后如果主機恢復了是切換回來還是自動變備機呢?需不需要人工二次確認一下?
這些問題可能都得根據(jù)業(yè)務(wù)的特性來得出答案,此處僅給出三種常見的雙機切換模式:
互連式:兩臺機器直接連接傳遞信息,并根據(jù)傳遞的狀態(tài)信息判斷是否要切換主機,如果通道本身發(fā)生故障則無法判斷是否要切換了,可以再增加一個通道構(gòu)成雙通道保障,不過也只是降低同時故障的概率。中介式:通過第三方中介來收集機器狀態(tài)并執(zhí)行策略,如果通道發(fā)生斷連,中介可以直接切換其他機器作為主機,但這要求中介本身是高可用的,已經(jīng)有比較成熟的開源解決方案如zookeeper、keepalived。模擬式:備機模擬成客戶端,向主機發(fā)送業(yè)務(wù)類似的讀寫請求,根據(jù)響應情況來判斷主機的狀態(tài)決定是否要切換主機,這樣可以最真實地感受到客戶端角度下的主機故障,但和互連式不同,能獲取到的其他機器信息很少,容易出現(xiàn)判斷偏差。最后一種雙機架構(gòu)是主主復制,和前面兩種只有一主的策略不同,這次兩臺都是主機,客戶端的請求可以達到任何一臺主機,不存在切換主機的問題。但這對數(shù)據(jù)的設(shè)計就有了嚴格的要求,如果存在唯一ID、嚴格的庫存數(shù)量等數(shù)據(jù),就無法適用,這種策略適合那些偏臨時性、可丟失、可覆蓋的數(shù)據(jù)場景。
數(shù)據(jù)集群:
采用雙機架構(gòu)的前提是一臺主機能夠存儲所有的業(yè)務(wù)數(shù)據(jù)并處理所有的業(yè)務(wù)請求,但機器的存儲和處理能力是有上限的,在大數(shù)據(jù)場景下就需要多臺服務(wù)器來構(gòu)成數(shù)據(jù)集群。
如果是因為處理能力達到瓶頸,此時可以增加從機幫主機分擔壓力,即一主多從,稱為數(shù)據(jù)集中集群。這種集群方式需要任務(wù)分配算法將請求分散到不同機器上去,主要的問題在于數(shù)據(jù)需要復制到多臺從機,數(shù)據(jù)的一致性保障會比一主一從更為復雜。且當主機故障時,多臺從機協(xié)商新主機的策略也會變得復雜。這里有開源的zookeeper ZAB算法可以直接參考。
如果是因為存儲量級達到瓶頸,此時可以將數(shù)據(jù)分散存儲到不同服務(wù)器,每臺服務(wù)器負責存儲一部分數(shù)據(jù),同時也備份一部分數(shù)據(jù),稱為數(shù)據(jù)分散集群。數(shù)據(jù)分散集群同樣需要做負載均衡,在數(shù)據(jù)分區(qū)的分配上,hadoop采用獨立服務(wù)器負責數(shù)據(jù)分區(qū)的分配,ES集群通過選舉一臺服務(wù)器來做數(shù)據(jù)分區(qū)的分配。除了負載均衡,還需要支持擴縮容,此外由于數(shù)據(jù)是分散存儲的,當部分服務(wù)器故障時,要能夠?qū)⒐收戏?wù)器的數(shù)據(jù)在其他服務(wù)器上恢復,并把原本分配到故障服務(wù)器的數(shù)據(jù)分配到其他正常的服務(wù)器上,即分區(qū)容錯性。
數(shù)據(jù)分區(qū):
數(shù)據(jù)集群可以在單臺乃至多臺服務(wù)器故障時依然保持業(yè)務(wù)可用,但如果因為地理級災難導致整個集群都故障了(斷網(wǎng)、火災等),那整個服務(wù)就不可用了。面對這種情況,就需要基于不同地理位置做數(shù)據(jù)分區(qū)。
做不同地理位置的數(shù)據(jù)分區(qū),首先要根據(jù)業(yè)務(wù)特性制定分區(qū)規(guī)則,大多還是按照地理位置提供的服務(wù)去做數(shù)據(jù)分區(qū),比如中國區(qū)主要存儲中國用戶的數(shù)據(jù)。
既然分區(qū)是為了防災,那么一個分區(qū)肯定不止存儲自身的數(shù)據(jù),還需要做數(shù)據(jù)備份。從數(shù)據(jù)備份的策略來說,主要有三種模式:
集中式:存在一個總備份中心,所有的分區(qū)數(shù)據(jù)都往這個總中心備份,設(shè)計起來簡單,各個分區(qū)間沒有聯(lián)系,不會互相影響,也很容易擴展新的分區(qū)。但總中心的成本較高,而且總中心如果出故障,就要全部重新備份?;涫剑好總€分區(qū)備份另一個分區(qū)的數(shù)據(jù),可以形成一個備份環(huán),或者按地理位置遠近來搭對備份,這樣可以直接利用已有的設(shè)備做數(shù)據(jù)備份。但設(shè)計較復雜,各個分區(qū)間需要聯(lián)系,當擴展新分區(qū)時,需要修改原有的備份線路。獨立式:每個分區(qū)配備自己的備份中心,一般設(shè)立在分區(qū)地理位置附近的城市,設(shè)計也簡單,各個分區(qū)間不會影響,擴展新分區(qū)也容易。但是成本會很高,而且只能防范城市級的災難。從存儲高可用的思路可以看出,高可用主要是通過增加機器冗余來實現(xiàn)備份,對計算高可用來說也是如此。通過增加機器,分擔服務(wù)器的壓力,并在單機發(fā)生故障的時候?qū)⒄埱蠓峙涞狡渌麢C器來保障業(yè)務(wù)可用性。
因此計算高可用的復雜性也主要是在多機器下任務(wù)分配的問題,比如當任務(wù)來臨(比如客戶端請求到來)時,如何選擇執(zhí)行任務(wù)的服務(wù)器?如果任務(wù)執(zhí)行失敗,如何重新分配呢?這里又可以回到前文說過的負載均衡相關(guān)的解法上。
計算服務(wù)器和存儲服務(wù)器在多機器情況下的架構(gòu)是類似的,也分為主備、主從和集群。
主備架構(gòu)下,備機僅僅用作冗余,平常不會接收到客戶端請求,當主機故障時,備機才會升級為主機提供服務(wù)。備機分為冷備和溫備。冷備是指備機只準備好程序包和配置文件,但實際平常并不會啟動系統(tǒng)。溫備是指備機的系統(tǒng)是持續(xù)啟動的,只是不對外提供服務(wù),從而可以隨時切換主機。
主從架構(gòu)下,從機也要執(zhí)行任務(wù),由任務(wù)分配器按照預先定義的規(guī)則將任務(wù)分配給主機和從機。相比起主備,主從可以發(fā)揮一定的從機性能,避免成本空費,但任務(wù)的分配就變得復雜一些。
集群架構(gòu)又分為對稱集群和非對稱集群。
對稱集群也叫負載均衡集群,其中所有的服務(wù)器都是同等對待的,任務(wù)會均衡地分配到每臺服務(wù)器。此時可以采用隨機、輪詢、Hash等簡單的分配機制,如果某臺服務(wù)器故障,不再給他分配任務(wù)即可。
非對稱集群下不同的服務(wù)器有不同的角色,比如分為master和slave。此時任務(wù)分配器需要有一定的規(guī)則將任務(wù)分配給不同角色的服務(wù)器,還需要有選舉策略來在master故障時選擇新的master。這個選舉策略的復雜度就豐儉由人了。
異地多活:
講存儲高可用已經(jīng)說過數(shù)據(jù)分區(qū),計算高可用也有類似的高可用保障思路,歸納來說,它們都可以根據(jù)需要做異地多活,來提高整體的處理能力,并防范地區(qū)級的災難。異地多活中的”異地“,就是指集群部署到不同的地理位置,“活”則強調(diào)集群是隨時能提供服務(wù)的,不同于“備”還需要一個切換過程。
按照規(guī)模,異地多活可以分為同城異區(qū)、跨城異地和跨國異地。顯而易見,不同模式下能夠應對的地區(qū)級故障是越來越高的,但同樣的,距離越遠,通信成本與延遲就越高,對通信通道可用性的挑戰(zhàn)也越高。因此跨城異地已經(jīng)不適合對數(shù)據(jù)一致性要求非常高的業(yè)務(wù),而跨國異地往往是用來給不同國家的用戶提供不同服務(wù)的。
由于異地多活需要花費很高的成本,極大地增加系統(tǒng)復雜度,因此在設(shè)計異地多活架構(gòu)時,可以不用強求為所有業(yè)務(wù)都做異地多活,可以優(yōu)先為核心業(yè)務(wù)實現(xiàn)異地多活。盡量保障絕大部分用戶的異地多活,對于沒能保障的用戶,通過掛公告、事后補償、完善失敗提示等措施進行安撫、提升體驗。畢竟要做到100%可用性是不可能的,只能在能接受的成本下盡量逼近,所以當可用性達到一定瓶頸后,補償手段的成本或許更低。
在異地部署的情況下,數(shù)據(jù)一定會冗余存儲,物理上就無法實現(xiàn)絕對的實時同步,且距離越遠對數(shù)據(jù)一致性的挑戰(zhàn)越大,雖然可以靠減少距離、搭建高速專用網(wǎng)絡(luò)等方式來提高一致性,但也只是提高而已,因此大部分情況下, 只需考慮保障業(yè)務(wù)能接受范圍下的最終一致性即可。
在同步數(shù)據(jù)的時候,可以采用多種方式,比如通過消息隊列同步、利用數(shù)據(jù)庫自帶的同步機制同步、
通過換機房重試來解決同步延遲問題、通過session id讓同一數(shù)據(jù)的請求都到同一機房從而不用同步等。
可見,整個異地多活的設(shè)計步驟首先是對業(yè)務(wù)分級,挑選出核心業(yè)務(wù)做異地多活,然后對需要做異地多活的數(shù)據(jù)進行特征分析,考慮數(shù)據(jù)量、唯一性、實時性要求、可丟失性、可恢復性等,根據(jù)數(shù)據(jù)特性設(shè)計數(shù)據(jù)同步的方案。最后考慮各種異常情況下的處理手段,比如多通道同步、日志記錄恢復、用戶補償?shù)?,此時可以借用前文所說的FMEA等方法進行分析。
接口級故障:
前面討論的都是較為宏觀的服務(wù)器、分區(qū)級的故障發(fā)生時該怎么辦,實際上在平常的開發(fā)中,還應該防微杜漸,從接口粒度的角度,來防范和應對接口級的故障。應對的核心思路依然是優(yōu)先保障核心業(yè)務(wù)和絕大部分用戶可用。
對于接口級故障,有幾個常用的方法:限流、排隊、降級、熔斷。其中限流和排隊屬于事前防范的措施,而降級和熔斷屬于接口真的故障后的處理手段。
限流的目的在于控制接口的訪問量,避免被高頻訪問沖垮。
從限流維度來說,可以基于請求限流,即限制某個指標下、某個時間段內(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)計周期來解決臨界點問題。
桶算法分為漏桶和令牌桶。
漏桶算法是將請求放入桶中,處理單元從桶里拿請求去進行處理,如果桶堆滿了就丟棄掉新的請求,可以理解為桶下面有個漏斗將請求往處理單元流動,整個桶的容量是有限的。這種模式下流入的速率取決于請求的頻率,當桶內(nèi)有堆積的待處理請求時,流出速率是勻速的。漏桶算法適用于瞬時高并發(fā)的場景(如秒殺),處理可能慢一點,但可以緩存部分請求不丟棄。
令牌桶算法是在桶內(nèi)放令牌,令牌數(shù)是有限的,新的請求需要先到桶里拿到令牌才能被處理,拿不到就會被丟棄。和漏桶勻速流出處理不同,令牌桶還能通過控制放令牌的速率來控制接收新請求的頻率,對于突發(fā)流量,可靠累計的令牌來處理,但是相對的處理速度也會突增。令牌桶算法適用于控制第三方服務(wù)訪問速度的場景,防止壓垮下游。
除了限流,還有一種控制處理速度的方法就是排隊。當新請求到來后先加入隊列,出隊端通過固定速度出隊處理請求,避免處理單元壓力過大。隊列也有長度限制,其機制和漏桶算法差不多。
如果真的事前防范真的被突破了,接口很可能或已經(jīng)發(fā)生了故障,還能做什么呢?
一種手段是熔斷,即當處理量達到閾值,就主動停掉外部接口的訪問能力,這其實也是一種防范措施,對外的表現(xiàn)雖然是接口訪問故障,但系統(tǒng)內(nèi)部得以被保護,不會引起更大的問題,待存量處理被消化完,或者外部請求減弱,或完成擴容后,再開放接口。熔斷的設(shè)計主要是閾值,需要按照業(yè)務(wù)特點和統(tǒng)計數(shù)據(jù)制定。
當接口故障后(無論是被動還是主動斷開),最好能提供降級策略。降級是丟車保帥,放棄一下非核心業(yè)務(wù),保障核心業(yè)務(wù)可用,或者最低程度能提供故障公告,讓用戶不要反復嘗試請求來加重問題了。比起手動降級,更好的做法也是自動降級,需要具備檢測和發(fā)現(xiàn)降級時機的機制。
再回顧一遍互聯(lián)網(wǎng)行業(yè)的金科玉律:只有變化才是不變的。在設(shè)計架構(gòu)時,一開始就要抱著業(yè)務(wù)隨時可能變動導致架構(gòu)也要跟著變動的思想準備去設(shè)計,差別只在于變化的快慢而已。因此在設(shè)計架構(gòu)時一定是要考慮可擴展性的。
在思考怎樣才是可擴展的時候,先想一想平常開發(fā)中什么情況下會覺得擴展性不好?大都是因為系統(tǒng)龐大、耦合嚴重、牽一發(fā)而動全身。因此對可擴展架構(gòu)設(shè)計來說,基本的思想就是拆分。
拆分也有多種指導思想,如果面向業(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)很少有純單機的服務(wù),因此至少都是C/S架構(gòu)、B/S架構(gòu),也就是至少也分為了客戶端/瀏覽器和后臺服務(wù)器這兩層。如果進一步拆分,就會將后臺服務(wù)基于職責進行自頂向下的劃分,比如分為接入層、應用層、邏輯層、領(lǐng)域?qū)拥取?/p>
分層的目的當然是為了讓各個層次間的服務(wù)減少耦合,方便進行各自范疇下的優(yōu)化,因此需要保證各層級間的差異是足夠清晰、邊界足夠明顯的,否則當要增加新功能的時候就會不知道該放到哪一層。各個層只處理本層邏輯,隔離關(guān)注點。
額外需注意的是一旦確定了分層,請求就必須層層傳遞,不能跳層,這是為了避免架構(gòu)混亂,增加維護和擴展的復雜度,比如為了方便直接跨層從接入層調(diào)用領(lǐng)域?qū)硬樵償?shù)據(jù),當需要進行統(tǒng)一的邏輯處理時,就無法切面處理所有請求了。
SOA架構(gòu):
SOA架構(gòu)更多出現(xiàn)在傳統(tǒng)企業(yè)中,其主要解決的問題是企業(yè)中IT建設(shè)重復且效率低下,各部門自行接入獨立的IT系統(tǒng),彼此之間架構(gòu)、協(xié)議都不同,為了讓各個系統(tǒng)的服務(wù)能夠協(xié)調(diào)工作,SOA架構(gòu)應運而生。
其有三個關(guān)鍵概念:服務(wù)、ESB和松耦合。
服務(wù)是指各個業(yè)務(wù)功能,比如原本各部門原本的系統(tǒng)提供的服務(wù),可大可小。由于各服務(wù)之間無法直接通信,因此需要ESB,即企業(yè)服務(wù)總線進行對接,它將不同服務(wù)連接在一起,屏蔽各個服務(wù)的不同接口標準,類似計算機中的總線。松耦合是指各個服務(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ù)的粒度會劃分地更細,對自動化部署能力也就要求更高,否則部署成本太大,達不到輕量快速的目的。
當然微服務(wù)雖然很火,但也不是解決所有問題的銀彈,它也會有一些問題存在。如果服務(wù)劃分的太細,那么互相之間的依賴關(guān)系就會變得特別復雜,服務(wù)數(shù)量、接口量、部署量過多,團隊的效率可能大降,如果沒有自動化支撐,交付效率會很低。由于調(diào)用鏈太長(多個服務(wù)),因此性能也會下降,問題定位會更困難,如果沒有服務(wù)治理的能力,管理起來會很混亂,不知道每個服務(wù)的情況如何。
因此如何拆分服務(wù)就成了每個使用微服務(wù)架構(gòu)的團隊的重要考量點。這里也提供一些拆分的思路:
三個火槍手原則:考慮每三個人負責一個服務(wù),互相可以形成穩(wěn)定的人員備份,討論起來也更容易得出結(jié)論,在此基礎(chǔ)上考慮能負責多大的一個服務(wù)?;跇I(yè)務(wù)邏輯拆分:最直觀的就是按邏輯拆分,如果職責不清,就參考三個火槍手原則確定服務(wù)大小?;诜€(wěn)定性拆分:按照服務(wù)的穩(wěn)定性分為穩(wěn)定服務(wù)和變動服務(wù),穩(wěn)定服務(wù)粒度可以粗一些,變動服務(wù)粒度可以細一些,目的是減少變動服務(wù)之間的影響,但總體數(shù)量依然要控制?;诳煽啃圆鸱郑喊凑湛煽啃耘判颍蟾叩目梢圆鸺氁恍?,由前文可知,服務(wù)越簡單,高可用方案就會越簡單,成本也會越低。優(yōu)先保障核心服務(wù)的高可用?;谛阅懿鸱郑侯愃瓶煽啃裕阅芤笤礁叩?,拆出來單獨做高性能優(yōu)化,可有效降低成本。微服務(wù)架構(gòu)如果沒有完善的基礎(chǔ)設(shè)施保障服務(wù)治理,那么也會帶來很多問題,降低效率,因此根據(jù)團隊和業(yè)務(wù)的規(guī)模,可以按以下優(yōu)先級進行基礎(chǔ)設(shè)施的支持:
優(yōu)先支持服務(wù)發(fā)現(xiàn)、服務(wù)路由、服務(wù)容錯(重試、流控、隔離),這些是微服務(wù)的基礎(chǔ)。接著支持接口框架(統(tǒng)一的協(xié)議格式與規(guī)范)、API網(wǎng)關(guān)(接入鑒權(quán)、權(quán)限控制、傳輸加密、請求路由等),可以提高開發(fā)效率。然后支持自動化部署、自動化測試能力,并搭建配置中心,可以提升測試和運維的效率。最后支持服務(wù)監(jiān)控、服務(wù)跟蹤、服務(wù)安全(接入安全、數(shù)據(jù)安全、傳輸安全、配置化安全策略等)的能力,可以進一步提高運維效率。微內(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)核,負責解析規(guī)則,并將輸入通過規(guī)則處理后得到輸出。而各種規(guī)則則是插件,通常根據(jù)各種業(yè)務(wù)場景進行配置,存儲到數(shù)據(jù)庫中。
人們通常把某項互聯(lián)網(wǎng)業(yè)務(wù)的發(fā)展分為四個時期:初創(chuàng)期、發(fā)展期、競爭期和成熟期。
在初創(chuàng)期通常求快,系統(tǒng)能買就買,能用開源就用開源,能用的就是好的,先要活下來;到了發(fā)展期開始堆功能和優(yōu)化,要求能快速實現(xiàn)需求,并有余力對一些系統(tǒng)的問題進行優(yōu)化,當優(yōu)化到頂?shù)臅r候就需要從架構(gòu)層面來拆分優(yōu)化了;進入競爭期后,經(jīng)過發(fā)展期的快速迭代,可能會存在很多重復造輪子和混亂的交互,此時就需要通過平臺化、服務(wù)化來解決一些公共的問題;最后到達成熟期后,主要在于補齊短板,優(yōu)化弱項,保障系統(tǒng)的穩(wěn)定。
在整個發(fā)展的過程中,同一個功能的前后要求也是不同的,隨著用戶規(guī)模的增加,性能會越來越難保障,可用性問題的影響也會越來越大,因此復雜度就來了。
對于架構(gòu)師來說,首要的任務(wù)是從當前系統(tǒng)的一大堆紛繁復雜的問題中識別出真正要通過架構(gòu)重構(gòu)來解決的問題,集中力量快速突破,但整體來說,要徐徐圖之,不要想著用重構(gòu)來一次性解決所有問題。
對項目中的問題做好分類,劃分優(yōu)先級,先易后難,才更容易通過較少的資源占用,較快地得到成果,提高士氣。然后再循序漸進,每個階段控制在1~3個月,穩(wěn)步推進。
當然,在這個過程中,免不了和上下游團隊溝通協(xié)作,需要注意的是自己的目標和其他團隊的目標可能是不同的,需要對重構(gòu)的價值進行換位思考,讓雙方都可以合作共贏,才能借力前進。
還是回到開頭的那句話,架構(gòu)設(shè)計的主要目的是為了解決軟件系統(tǒng)復雜度帶來的問題。首先找到主要矛盾在哪,做到有的放矢,然后再結(jié)合知識、經(jīng)驗進行設(shè)計,去解決面前的問題。
祝人人都成為一名合格的架構(gòu)師。
關(guān)鍵詞:
Copyright 2015-2023 今日藝術(shù)網(wǎng) 版權(quán)所有 備案號:滬ICP備2023005074號-40 聯(lián)系郵箱:5 85 59 73 @qq.com