當(dāng)前很多應(yīng)用都是data-intensive
型,而非compute-intensive
旷坦。如
- 存儲數(shù)據(jù)后供其他應(yīng)用訪問(Database)
- 記錄一些比較重的操作的結(jié)果用以加速獲取操作(Cache)
- 支持用戶通過關(guān)鍵詞或者過濾器檢索數(shù)據(jù)(search index)
- 通過發(fā)消息到進(jìn)程以異步化(stream processing)
- 定期獲取大量數(shù)據(jù)(batch processing)
這些(data-intensive
)應(yīng)用各自擁有各自的特性,我們需要根據(jù)他們提供的能力來設(shè)計(jì)我們的應(yīng)用佑稠,甚至在當(dāng)前沒有符合我們使用場景的應(yīng)用組件時秒梅,構(gòu)建相應(yīng)的組件。
關(guān)于數(shù)據(jù)系統(tǒng)的思考
我們是做業(yè)務(wù)系統(tǒng)開發(fā)的舌胶,為什么我們需要思考data-intensive
應(yīng)用捆蜀?
在過去,一個業(yè)務(wù)系統(tǒng)使用的數(shù)據(jù)應(yīng)用可能比較單一幔嫂,一個數(shù)據(jù)庫就可以完成全部數(shù)據(jù)的處理辆它,表的join、唯一性控制履恩、存儲優(yōu)化锰茉、事務(wù)等等。現(xiàn)在來看切心,各種數(shù)據(jù)應(yīng)用所承擔(dān)的責(zé)任越來越精專:rdbms飒筑、nosql、mq昙衅、cache扬霜、es定鸟,我們選擇每個數(shù)據(jù)應(yīng)用時都要考慮這些應(yīng)用的專長與缺陷而涉。以至于業(yè)務(wù)系統(tǒng)開發(fā)時我們不得不去思考:如何保證緩存與主存的一致性、分布式事務(wù)方案選型联予、使用MQ異步化啼县、高可用及降級。
很多方面都會影響一個數(shù)據(jù)系統(tǒng)的設(shè)計(jì)沸久,包括但不限于:開發(fā)者經(jīng)驗(yàn)季眷、依賴的遺留系統(tǒng)、交付周期卷胯、組織對各種風(fēng)險(xiǎn)的忍受力及其他限制子刮。
這里主要關(guān)注三個層面:
- 可靠性
- 伸縮性
- 可維護(hù)性
可靠性
軟件系統(tǒng)關(guān)于可靠性的期望包括:
- 符合用戶預(yù)期的表現(xiàn)
- 是否能支持用戶的異常行為(輸入、操作等)
- 高負(fù)載、大數(shù)據(jù)量的情況下對要求的用戶場景是否能表現(xiàn)良好
- 系統(tǒng)可以防止越權(quán)(unauthorized access)和濫用(abuse)
簡言之:無論如何都能正常表現(xiàn)(continuing to work correctly, even when thingsgo wrong
)
正常情況下的符合預(yù)期是軟件系統(tǒng)最基本保證的挺峡。當(dāng)系統(tǒng)出現(xiàn)問題時葵孤,我們稱之fault,對于能夠抵御錯誤的系統(tǒng)我們稱之容錯能力橱赠。但是并非所有的問題都可以被抵銷或避免尤仍,比如說地球毀滅。所以我們所說的容錯也指特定類型狭姨。這里還要區(qū)分fault
和failure
: -
fault
:也是在spec中描述的宰啦。fault不能百分之百避免,所以需要系統(tǒng)能夠處理可預(yù)知的fault饼拍,防止fault導(dǎo)致failure -
failure
:在意料之外的赡模,當(dāng)failure出現(xiàn)時,系統(tǒng)將不再對外提供服務(wù)
和直覺相反的是师抄,對于容錯能力比較強(qiáng)的系統(tǒng)是經(jīng)常觸發(fā)一些fault(斷網(wǎng)演練纺裁、隨機(jī)切斷進(jìn)程等)的系統(tǒng),只有通過觸發(fā)fault才能夠檢驗(yàn)容錯機(jī)制是否可靠司澎,很多時候系統(tǒng)問題來自于對于異常的處理之上欺缘。就像Netflix引入chaos monkey隨機(jī)產(chǎn)生問題來驗(yàn)證系統(tǒng)的容錯能力。
硬件錯誤
據(jù)報(bào)告稱一個硬件發(fā)生問題的平均時長為10-50年挤安,對于一個10000塊磁盤的集群來說谚殊,按概率平均一天就會有一塊磁盤發(fā)生問題。
對于這類問題蛤铜,常規(guī)解決方案就是冗余嫩絮,比如說通過RAID卡、備用電源围肥、可熱替換CPU等方式剿干,這些方法不能避免硬件發(fā)生問題,而是在發(fā)生問題時能夠讓運(yùn)行其上的系統(tǒng)無感知硬件問題以長時間運(yùn)行穆刻。
隨著集群規(guī)模的擴(kuò)大置尔,硬件故障發(fā)生的越來越頻繁,也越來越傾向使用軟件或系統(tǒng)設(shè)計(jì)方式來解決硬件故障氢伟,如數(shù)據(jù)中心榜轿、多機(jī)房、軟負(fù)載等朵锣。
軟件錯誤
硬件問題大多隨機(jī)發(fā)生谬盐,但是大面積的硬件問題同時爆發(fā)確實(shí)小概率事件。軟件系統(tǒng)異常則更難預(yù)測诚些,且通常一個軟件問題可能會關(guān)聯(lián)其他系統(tǒng)飞傀、節(jié)點(diǎn)服務(wù),導(dǎo)致大面積不可用。如:
- 由于軟件bug某個特定輸入導(dǎo)致所有節(jié)點(diǎn)崩潰(如之前Stack Overflow發(fā)生的regex解析cpu耗時過高)砸烦。
- 失控的線程消耗過多的系統(tǒng)資源
- 依賴服務(wù)耗時過多拖垮當(dāng)前應(yīng)用犀被,或者返回異常信息對當(dāng)前應(yīng)用造成影響
- 雪崩。一個小的問題引發(fā)一連串的故障外冀,從而導(dǎo)致整個服務(wù)不可用
通常導(dǎo)致這些問題的bug在系統(tǒng)中隱藏很久寡键,當(dāng)特定的實(shí)際時被觸發(fā)。一個系統(tǒng)運(yùn)行正常是基于一些假設(shè)雪隧,通常在這些假定的環(huán)境下系統(tǒng)是不會發(fā)生問題的西轩,而且在一段時間內(nèi)這些假定也是成立的,不過當(dāng)這些假定不成立時脑沿,就可能發(fā)生一些不可知的情況藕畔。
沒有一個好的方案避免上述所有問題,不過一些小細(xì)節(jié)可以帶來些助益:
- 仔細(xì)思考與系統(tǒng)交互的環(huán)境與場景
- 完備的測試
- process isolation
- 進(jìn)程能夠崩潰及重啟 #對于不允許崩潰的系統(tǒng)庄拇,就要保證足夠健壯注服,否則墨菲定律教你做人
- 生產(chǎn)環(huán)境下監(jiān)控、測量措近、分析
如果一個系統(tǒng)確保其行為可靠溶弟,則需要時常進(jìn)行自檢,且當(dāng)出現(xiàn)與預(yù)期不符是能夠及時告警瞭郑。
人為錯誤
系統(tǒng)的問題歸根結(jié)底是人的問題辜御。
人設(shè)計(jì)、搭建系統(tǒng)屈张,維護(hù)系統(tǒng)運(yùn)轉(zhuǎn)的也是人擒权。
基于不可靠的「人」如何設(shè)計(jì)、構(gòu)建可靠系統(tǒng):
- 設(shè)計(jì)系統(tǒng)以盡可能低的可能發(fā)生錯誤阁谆。如:設(shè)計(jì)良好的抽象碳抄、API能夠讓使用者容易選擇正確的姿勢而非錯誤的方式。但是如果接口設(shè)計(jì)太死场绿,用戶則會嘗試使用他們更舒服的姿勢包裝(work around)再使用剖效,這點(diǎn)是需要權(quán)衡的。
- 將最可能發(fā)生問題的行為與最可能導(dǎo)致故障環(huán)境隔離裳凸。比如構(gòu)建預(yù)發(fā)環(huán)境贱鄙,避免生產(chǎn)環(huán)境測試。
- 完備的測試姨谷。從單元測試到系統(tǒng)集成測試以及人工測試。自動化測試不僅覆蓋大部分場景映九,也要覆蓋那些比較少觸發(fā)的邊緣場景梦湘。
- 能夠簡單、快速地從人為異常中恢復(fù),將異常所造成的影響降低到最小捌议。如提供配置回滾機(jī)制哼拔、灰度發(fā)布、一些工具能夠?qū)ο⑦M(jìn)行重演來修復(fù)數(shù)據(jù)計(jì)算異常等瓣颅。
- 構(gòu)建清晰詳盡異常監(jiān)控倦逐,包括但不限性能指標(biāo)、錯誤率等宫补。監(jiān)控可以在異常發(fā)生前進(jìn)行預(yù)警檬姥,在異常發(fā)生后幫助分析
- 項(xiàng)目管理
為什么可靠性很重要:
伸縮性
一個當(dāng)前穩(wěn)定的系統(tǒng)在將來不一定仍舊穩(wěn)定,除非是一個非核心應(yīng)用或者一個不再有新需求或用戶增長的產(chǎn)品粉怕。當(dāng)一個產(chǎn)品承載的請求量從1kw增長到1b健民,原有應(yīng)用服務(wù)一定會遇到挑戰(zhàn)。
伸縮性
指當(dāng)應(yīng)用請求增長時贫贝,可以通過一定手段使應(yīng)用能夠繼續(xù)承載新的流量壓力秉犹。我們需要常常問自己:當(dāng)系統(tǒng)擴(kuò)張、用戶請求增長稚晚、數(shù)據(jù)增多崇堵,我們該如何應(yīng)對?
負(fù)載
QPS客燕?IOPS筑辨?并發(fā)請求數(shù)?緩存命中幸逆?這些指標(biāo)都是單一層面棍辕,對于一個系統(tǒng)而言需要從更全局的角度來度量。以twitter為例还绘,主要操作包括:
- 發(fā)推:用戶發(fā)送一個推文到其跟隨者(followers)(4.6k均值QPS楚昭,12k峰值QPS)
- 時間線:用戶可以看到他關(guān)注者的推文列表(300k QPS)
如果單純處理12kQPS寫入操作其實(shí)很簡單,但是Twitter面臨的伸縮性挑戰(zhàn)不是推文容量拍顷,而是fan-out:每個用戶關(guān)注多個用戶抚太,每個用戶也被很多用戶所關(guān)注。上述兩個操作真正實(shí)現(xiàn)有兩種:
- 用戶發(fā)推文昔案,將其存儲到集中存儲中尿贫。當(dāng)用戶拉取自己的時間線時,需要將自己關(guān)注的所有用戶的推文拉取出來踏揣,并且按照一定的規(guī)則進(jìn)行排序庆亡。
- 維護(hù)一個緩存來存儲用戶時間限,當(dāng)用戶發(fā)表新推文捞稿,則查找所有關(guān)注該用戶跟隨者的時間線緩存瓶堕,并更新該推文。
當(dāng)然實(shí)際Twitter并不是這樣實(shí)現(xiàn)涡上,但是可以作為一個典型的場景來說明負(fù)載不是單純的來自于入口側(cè)的流量颤难,與具體的業(yè)務(wù)場景及實(shí)現(xiàn)方式有關(guān)。
性能
負(fù)載是外部因素,性能是具體表現(xiàn)。當(dāng)負(fù)載發(fā)生變化時,可以用以下兩種方式表述系統(tǒng)的:
- 負(fù)載升高系統(tǒng)資源不調(diào)整继阻,此時系統(tǒng)性能如何?
- 負(fù)載升高废酷,通過增加多少資源可以保持系統(tǒng)性能保持恒定瘟檩?
性能也是一系列指標(biāo),如web應(yīng)用為平均請求耗時锦积、數(shù)據(jù)庫為讀寫速率芒帕、大數(shù)據(jù)應(yīng)用為吞吐量。
通常有一系列數(shù)值可以用來衡量性能丰介,如:
- 平均數(shù):最常用指標(biāo)背蟆,但是并不能完全反映實(shí)際情況
- 中位數(shù):用以反映半數(shù)用戶實(shí)際獲得的性能
- 99/95百分位數(shù):絕大部分占比用戶實(shí)際獲得性能∠保可以避免一些極端情況將整體均值拉大
- 高位:通常被稱為尾部延遲(tail lantencies)带膀。雖然這些請求占比很低,但是通常是一些臨界情況導(dǎo)致橙垢,如一些大商家數(shù)據(jù)垛叨、大批量請求、訪問部分問題機(jī)器等柜某。對于大商家用戶的請求需要格外關(guān)注嗽元,如果忽略尾部延遲的話可能會造成大商家用戶體驗(yàn)喪失∥够鳎客戶端通常都會使用重試作為failover機(jī)制剂癌,當(dāng)請求超時時可能會觸發(fā)重試,對于尾部延遲由于客戶端重試可能會進(jìn)一步增加超時請求的觸發(fā)翰绊,此現(xiàn)象被稱為:
尾部延遲放大
(tail lantency amplification)
大規(guī)模集群的數(shù)值統(tǒng)計(jì)一定要有各自服務(wù)的統(tǒng)計(jì)數(shù)據(jù)佩谷,全局平均會掩蓋部分機(jī)器的異常。
通常會使用SLO/SLA來約定服務(wù)性能监嗜,因?yàn)橐恍╇S機(jī)事件就是可能會導(dǎo)致系統(tǒng)出現(xiàn)各種各樣的問題谐檀。對于性能的追求也要考慮投入產(chǎn)出比。
注意響應(yīng)時間測量不能只測量server端的處理時間裁奇,因?yàn)橛行┞埱罂赡軙?dǎo)致請求堆積桐猬,對于服務(wù)端而言小部分請求處理時間長,后續(xù)請求處理時間很短框喳,但是對于客戶端而言大部分的請求的耗時因?yàn)槎逊e而變長课幕。因而可以考慮記錄客戶端請求的時延厦坛,可以保證最真實(shí)的服務(wù)質(zhì)量五垮。
壓測時也要注意各自壓測請求的獨(dú)立乍惊,等待前次響應(yīng)返回后再觸發(fā)后續(xù)可能會導(dǎo)致壓測流量下降。
如何處理負(fù)載
對于處理不同負(fù)載指標(biāo)的系統(tǒng)其架構(gòu)設(shè)計(jì)放仗、技術(shù)選型润绎、部署方式以及后續(xù)的擴(kuò)展方式(scale-out/scale-up)都會有所不同。一個系統(tǒng)是否能夠適于拓展诞挨,來自于對負(fù)載場景的分析莉撇,如果基于一個錯誤的假設(shè)(如后續(xù)流量瓶頸在本機(jī)CPU算力,但是實(shí)際在磁盤IO)惶傻,好一點(diǎn)來說整個擴(kuò)展方案僅僅是浪費(fèi)資源棍郎,糟糕的則會適得其反。
可維護(hù)性
一個軟件最愉悅的時間莫過于在起初開發(fā)的過程中银室,但是最最耗費(fèi)精力階段是在上線后的維護(hù)涂佃,問題處理、保證運(yùn)行蜈敢、適配新平臺辜荠、添加新特性、償還技術(shù)債務(wù)等抓狭。
幾乎大部分人都不喜歡與遺留系統(tǒng)打交道伯病,修改其他人的bug、維護(hù)陳年代碼否过、使用了一個幾乎沒人了解過往技術(shù)棧午笛。但是轉(zhuǎn)念想想,一個系統(tǒng)一旦開發(fā)發(fā)布上線之后苗桂,立馬就變成了別人的遺留系統(tǒng)药磺。如果沒有良好設(shè)計(jì),那么在別人接手之前誉察,你需要維護(hù)你這坨狗屎(如果確實(shí)是狗屎)与涡。所以在軟件系統(tǒng)實(shí)際是,需要對三個設(shè)計(jì)原則格外關(guān)注:
- 可操作性(Operability):便于運(yùn)維同學(xué)方便保證系統(tǒng)平滑運(yùn)行
- 簡單:可以讓新的工程師很好的了解該系統(tǒng)持偏,盡可能的將所有復(fù)雜邏輯移除驼卖。(與接口的簡單性是不同的)
- 可進(jìn)化(Evolvability):新增功能、系統(tǒng)變更鸿秆、適配未知需求等都很方便酌畜。也稱為:可擴(kuò)展性、可適配變化性卿叽、柔性等
可操作性:讓運(yùn)維更美好
Good operations can often work around the limitations of bad software, but good software cannot run reliably with bad operations
簡言之:有時候良好的操作性比好的軟件(設(shè)計(jì))更重要桥胞。
運(yùn)維團(tuán)隊(duì)(不僅僅指ops恳守,還包含devs)對于系統(tǒng)是否能良好執(zhí)行也起到至關(guān)重要的作用,其職責(zé)通常包括:
- 監(jiān)控系統(tǒng)健康情況贩虾,在系統(tǒng)異常時能夠迅速反應(yīng)
- 當(dāng)系統(tǒng)異炒吆妫或者性能下降時,能夠追根溯源
- 保持系統(tǒng)缎罢、平臺更新伊群,包括最新安全補(bǔ)丁
- 了解系統(tǒng)間關(guān)系,當(dāng)可能有問題變更發(fā)生時策精,能夠在真正發(fā)生問題前避免舰始。
- 預(yù)防可能發(fā)生的問題(容量預(yù)估)
- 良好的發(fā)布、配置變更等工具
- 執(zhí)行復(fù)雜的維護(hù)任務(wù)咽袜,如將應(yīng)用從一個平臺遷移到另一個平臺
- 流程定義丸卷,以使操作可期,保證環(huán)境穩(wěn)定
- 維護(hù)組織信息询刹,以便人員流動時仍能夠傳承
高可操作性意味著日常工作處理順暢谜嫉,可以讓運(yùn)維團(tuán)隊(duì)專注于更有價值的工作。同時數(shù)據(jù)系統(tǒng)也能提供相應(yīng)的支持范抓,如:
- 提供運(yùn)行時行為骄恶、系統(tǒng)內(nèi)運(yùn)行態(tài)可視化,以及相應(yīng)的監(jiān)控服務(wù)
- 避免單點(diǎn)依賴匕垫,允許節(jié)點(diǎn)動態(tài)摘除仍保證系統(tǒng)運(yùn)行穩(wěn)定
- 使用標(biāo)準(zhǔn)工具提供自動化及集成支持
- 良好文檔及易懂的運(yùn)維模型(如果操作X僧鲁,那么Y將發(fā)生)
- 提供可靠的默認(rèn)行為,同時提供管理員足夠權(quán)限
- 可期的行為可以自愈象泵,當(dāng)系統(tǒng)異常時(給予足夠權(quán)限)系統(tǒng)管理員能夠介入處理
- 行為可期寞秃,避免意外(「驚喜」)
簡單:處理復(fù)雜
當(dāng)需求不斷擴(kuò)充、系統(tǒng)變大時偶惠,必然會由簡單變得復(fù)雜春寿。一個系統(tǒng)變得復(fù)雜的表現(xiàn)通常為:
狀態(tài)的膨脹、模型緊耦合忽孽、糾纏不清的依賴绑改、不一致的命名和術(shù)語、為了解決性能問題而做的hack兄一、特殊場景的兼容方案等等厘线。
系統(tǒng)變得復(fù)雜以后,新的變更將會更易引入問題出革。當(dāng)系統(tǒng)讓開發(fā)者難以理解造壮,一些潛規(guī)則、未預(yù)料的后果骂束、不可預(yù)期交互更容易被忽視耳璧。因而讓系統(tǒng)變得簡單應(yīng)當(dāng)是我們很關(guān)鍵的目標(biāo)成箫。
Moseley和Marks如下定義復(fù)雜:
Complex as accidental if it is not inherent in the problem that the software solves(as seen by the users) but arise only from the implementation.
當(dāng)一個軟件產(chǎn)生了或解決了原本不是這個軟件應(yīng)該提供給用戶的功能或問題解決方案時,那么這些問題就是復(fù)雜度所引起的旨枯。
解決意外復(fù)雜度的一個利器是抽象
蹬昌。抽象隱藏了實(shí)現(xiàn)細(xì)節(jié),提供了簡單易懂的接口召廷,同時可以在很多場合進(jìn)行復(fù)用凳厢。不僅僅是因?yàn)閺?fù)用避免了重復(fù)開發(fā)所引入的新問題账胧,更可以通過對同一抽象的優(yōu)化可以實(shí)現(xiàn)使用該抽象的全部優(yōu)化竞慢。抽象也可以讓我們避免去理解那些不需要理解的細(xì)節(jié)內(nèi)容,如高級語言將機(jī)器語言進(jìn)行封裝治泥,對于一般的軟件開發(fā)者而言筹煮,使用Java并不需要關(guān)心其機(jī)器碼,可以讓開發(fā)人員更聚焦居夹。
可擴(kuò)展性(Evolvability):讓變更更容易
系統(tǒng)不可能一成不變:過去為預(yù)料的場景發(fā)生了败潦、新的業(yè)務(wù)需求、平臺更替准脂、架構(gòu)升級等等劫扒。
通過引入敏捷可以協(xié)助完成變更,同時進(jìn)可能避免問題發(fā)生狸膏。如引入TDD(Test-Driven Development)及重構(gòu)沟饥。
如上所說,簡單及抽象可以保證變更的可預(yù)期湾戳,所以一個擴(kuò)展性良好的系統(tǒng)也必然是足夠抽象簡單的贤旷。
小結(jié)
-
可靠性
:即便當(dāng)問題發(fā)生時,系統(tǒng)也能正常運(yùn)作砾脑。異秤资唬可能發(fā)生自硬件、軟件甚至更多來自人為盅藻。容錯技術(shù)可以在問題發(fā)生時讓用戶無感知 -
可擴(kuò)展性
:當(dāng)負(fù)載升高氏淑,系統(tǒng)也能通過相應(yīng)策略保證性能穩(wěn)定夸政。討論可擴(kuò)展性之前守问,需要先明確負(fù)載
及性能
分表代表什么。 -
可維護(hù)性
:簡言之就是讓運(yùn)維及開發(fā)在處理系統(tǒng)時能更從容。好的抽象能夠降低復(fù)雜度仿便,讓系統(tǒng)更易于變更及添加新功能嗽仪。系統(tǒng)指標(biāo)數(shù)據(jù)可視化也便于系統(tǒng)的維護(hù)闻坚。