留心那些潛在的系統(tǒng)設(shè)計問題
在系統(tǒng)設(shè)計階段考慮全面很難,有許多人傾向于把整個設(shè)計分成若干階段,在迭代中完成整個設(shè)計,這本身是非常好的,但是,就如同“先做出來,以后再優(yōu)化”這樣的經(jīng)典謊言一樣,本身并無錯,只是許多程序員都不習慣于真正的迭代設(shè)計和迭代優(yōu)化。舉例來說,有一個日益復(fù)雜的類,每個人都修改一點點,一直到最后都沒有人愿意去做重構(gòu),大家的心態(tài)都是一樣的:“我只修改了一點點,為什么要我去動那么大的刀,于我沒有任何好處”。我不在這里談?wù)撨@一問題的解決辦法,我倒是想說,在開始階段考慮清楚問題在多數(shù)情況下還是很有好處的,設(shè)計考慮得越是清楚,在后續(xù)階段代碼可以承受越多的變更而不腐朽。
再做系統(tǒng)設(shè)計的時候,我們常常會這樣說:“一般情況下”、“99%”和“基本上”等等。如果你發(fā)現(xiàn)這是在悄悄地,或者潛意識地避談問題,可就要小心了。有時候你可以找到根據(jù),“事情不會那么壞吧”,“不會那么不湊巧吧”,在系統(tǒng)設(shè)計階段盡把事情往好的方向想可未必是件好事;也許更多時候會覺得這是直覺,總覺得某一處設(shè)計別扭,不合理卻有說不出強硬的理由來,最多只能抱怨一句“通常它不應(yīng)該是這樣設(shè)計的”。這種情況發(fā)生的時候,請千萬不要放過它,很多次,在系統(tǒng)上線以后,最初的問題或者潛在的問題最終暴露出來,而這樣的問題很多在系統(tǒng)設(shè)計階段都是有端倪的。
例子1:用戶行為記錄的持久化
以前我參與做過這樣一個系統(tǒng),用戶的行為需要被記錄到數(shù)據(jù)庫里去,但是每條記錄發(fā)生的時候都寫一次數(shù)據(jù)庫覺得開銷太大,于是設(shè)計了一個鏈表:
- 用戶的行為會首先被即時記錄到鏈表里面去;
- 每十分鐘往數(shù)據(jù)庫里面集中寫一次數(shù)據(jù),然后清空鏈表內(nèi)的數(shù)據(jù)。
看起來確實可以實現(xiàn)需求,可是,這樣的設(shè)計有什么問題?
這樣的設(shè)計當時居然沒有受到系統(tǒng)設(shè)計評審的人的質(zhì)疑,我實在覺得奇怪。我想很多人都可以看得出潛在的問題:
- 清空鏈表數(shù)據(jù)是使用時間條件觸發(fā)的任務(wù)來完成,換言之,無論這十分鐘內(nèi)如果事件暴增,也無法觸發(fā)鏈表清空的行為,鏈表很容易變得非常大;
- 清空鏈表的任務(wù)如果執(zhí)行過程中出了異常,甚至僅僅是處理速度受到阻塞,將直接導(dǎo)致鏈表數(shù)據(jù)無法得到清空;
- 如果往數(shù)據(jù)庫里寫數(shù)據(jù)和清空鏈表的行為需要鎖定鏈表,倘若鏈表很大,或者寫數(shù)據(jù)庫過慢,都會導(dǎo)致鏈表寫行為被阻塞。
這些問題當然在明確的情況下可以得到規(guī)避,但是毫無疑問,這樣的設(shè)計充滿了潛在的危險。事實上,最終這樣的問題也確實發(fā)生了,導(dǎo)致的結(jié)果是鏈表巨大,撐死了整個系統(tǒng),OOM,系統(tǒng)失去響應(yīng)。
例子2:HashMap并發(fā)訪問導(dǎo)致死循環(huán)
非常常見的并發(fā)訪問HashMap的問題,我也遇到過。有潛在的危險導(dǎo)致HashMap死循環(huán),表現(xiàn)就是CPU占用100%,而且這樣的問題是不可逆的,問題的原因分析我相信大家可以在網(wǎng)上搜得到很多文章,我就不啰嗦了。我印象深刻的是當時定位完問題,向犯下錯誤的程序員解釋原因的時候,他居然還說:“這個HashMap的讀寫很不頻繁,哪有那么巧的事?”,這就是僥幸心理,即便知道了問題依然不愿意做出修正。
例子3:摘要算法的沖突問題
類似的問題還有,使用摘要算法的時候,比如MD5,我在做一個系統(tǒng),使用一個中心集群緩存,使用一個巨長的字符串的MD5摘要來做key,好處在于key的長度可以大大縮短,但我們都知道,任何摘要算法都會使得結(jié)果字符串存在沖突(重復(fù))的可能,即源字符串不同,但是摘要字符串相同,雖說用統(tǒng)計的話來說,單純兩個字符串發(fā)生這種情況的概率低到幾乎不可能發(fā)生。但是我們依然需要謹慎,尤其是在數(shù)據(jù)量巨大的情況下,一旦發(fā)生沖突,要有解決辦法(比如把源字符串放在緩存條目的結(jié)果對象中,在緩存條目命中,正式取出返回前,再進一步比較源字符串以確定100%的準確性),或者至少必須要能夠承擔風險。
例子4:文件處理后續(xù)流程的兩個問題
最近有一位同事向我們介紹了他最近處理的一個問題,這個問題是,用戶會上傳一個多行的文件,比如文件有一萬行,每一行都代表一條待處理的數(shù)據(jù),在數(shù)據(jù)正確的時候,一切都正常;倘若有一行數(shù)據(jù)處理發(fā)生錯誤,會自動發(fā)送一封郵件通知,看起來似乎很不錯的系統(tǒng)。但是這個時候問題來了,有一次文件的處理錯誤過多,導(dǎo)致一口氣發(fā)送了幾千封郵件,變成了郵件洪水。而在他介紹這個系統(tǒng)設(shè)計的時候,我們留意到了其中存在一個時間條件觸發(fā)的任務(wù),任務(wù)基于兩個數(shù)據(jù)庫的數(shù)據(jù)執(zhí)行,這兩個數(shù)據(jù)庫的數(shù)據(jù)同步是單獨完成的,因此可能存在數(shù)據(jù)不一致的情況,并且在這里假定在數(shù)據(jù)更新的一小時以后,兩個庫的數(shù)據(jù)就會一致了。這其實就涉及到了兩個問題或者隱患,一個是郵件處理和發(fā)送的數(shù)量缺乏控制,另一個是用假定的時間來保證數(shù)據(jù)的一致性。
例子5:單點故障問題
單點故障問題也是很常見的會導(dǎo)致服務(wù)失去的問題,出了問題所有人都知道原因,但是有時候就是很難在系統(tǒng)設(shè)計階段識別出來。以前我們給電信運營商提供服務(wù),很多電信運營商通常有錢(比如國內(nèi)的三家壟斷巨頭),不太在乎成本。服務(wù)器用的單板幾萬塊錢一塊,備了幾十塊,文件存儲是一個大型的磁盤陣列,數(shù)據(jù)庫是IBM小型機雙機備份(PS:IBM的設(shè)備其實挺不可靠的,聽維優(yōu)的同學說,保修期內(nèi)屁事兒沒有,保修期一到一臺臺IBM的機器開始壞,搞得像定時炸彈似的),當時唯獨忽略了單點的負載分擔硬件——F5,F(xiàn)5掛掉的時候,工程師都傻了眼。
例子6:文件不斷寫入導(dǎo)致磁盤滿的問題
文件寫滿磁盤導(dǎo)致空間不夠的例子也非常常見,絕大多數(shù)寫文件的場景大家都會留意到,并且在系統(tǒng)設(shè)計評審的時候都會有人站出來問,“xxx的文件寫入是否是可控的?”。但是,由于文件寫入的場景非常多,還是有很多情況被忽略。比如JVM的GC日志的打印,這樣的文件可以協(xié)助定位問題,但是如果不設(shè)置文件上限大小參數(shù),就有導(dǎo)致磁盤空間不足的風險;還有日志文件,絕大多數(shù)系統(tǒng)都有日志文件壓縮或者日志文件轉(zhuǎn)移的腳本,但是和前面提到的例子1一樣,一方是生產(chǎn)者,一方是消費者,消費者出了問題,就會導(dǎo)致數(shù)據(jù)堆積。如果這樣的文件處理腳本執(zhí)行出現(xiàn)問題,或者在系統(tǒng)壓力大以及系統(tǒng)異常情況頻繁的時候,日志瘋漲,來不及及時把日志文件轉(zhuǎn)移出去,導(dǎo)致日志文件把磁盤撐滿。通常對于要求比較高的服務(wù),磁盤空間監(jiān)控是必要的。
例子7:服務(wù)器掉電以后的快恢復(fù)
再說一個問題,這個問題是從一個技術(shù)分享中流傳開來的。亞馬遜網(wǎng)站的數(shù)據(jù)都是頁面服務(wù)器先從緩存服務(wù)中獲取數(shù)據(jù),通常這個命中率很高,如果獲取不到數(shù)據(jù)或者數(shù)據(jù)過期以后再到數(shù)據(jù)庫里查詢。這樣的模式非常常見,我們也總能看到很多技術(shù)報告里面寫平均的緩存命中率能夠達到百分之九十多,可以飆到多少多少的TPS,為此可以節(jié)約多少多少硬件成本。初看這樣的設(shè)計真不錯,但是很容易忽視的一點是,這樣的數(shù)據(jù)是建立在足夠長時間,以及足夠多統(tǒng)計數(shù)據(jù)的基礎(chǔ)之上的,但是在單個時間段內(nèi),緩存命中率可以低到難以承受的地步,導(dǎo)致底層的數(shù)據(jù)服務(wù)直接被沖垮。有一次亞馬遜機房突然掉電,在恢復(fù)的時候把網(wǎng)頁服務(wù)器都通上電,這時候緩存服務(wù)還幾乎沒有緩存數(shù)據(jù),緩存命中率幾乎為零,于是大量的請求沖向數(shù)據(jù)庫,直接把數(shù)據(jù)庫沖垮。外在的表現(xiàn)就是,掉電導(dǎo)致網(wǎng)站無法提供服務(wù),短期內(nèi)訪問恢復(fù),隨后又喪失服務(wù)能力。
軟件當中有些東西和經(jīng)驗有密切關(guān)系,不像很相對容易提高的語言技能和算法,系統(tǒng)設(shè)計經(jīng)驗,尤其是對問題的預(yù)估很需要時間和項目的磨煉。我不知道這樣的系統(tǒng)設(shè)計經(jīng)驗怎樣才能快速積累,但是我想還是有一些常規(guī)模式可循,我不知道是否有比較經(jīng)典的資料可以學習。另一方面,系統(tǒng)設(shè)計真是一個細致和謹慎的活兒,不要隨意放過那些潛在的問題,有時候甚至就是一點奇怪的感覺,或者是設(shè)計圖看起來不那么協(xié)調(diào)和穩(wěn)當,細究下去,還真能發(fā)現(xiàn)陷阱。如果你也有類似的經(jīng)歷,不妨談一談。
























