負(fù)載增長(zhǎng)時(shí)悄然襲來(lái)的42個(gè)怪獸問(wèn)題
隨著負(fù)載的增長(zhǎng),你精心設(shè)計(jì)的程序可能會(huì)遭遇很多突如其來(lái)的問(wèn)題:系統(tǒng)原有的平穩(wěn)將被打破,我們將對(duì)這些問(wèn)題逐一考察。當(dāng)然,你可以進(jìn)行橫向或縱向的擴(kuò)展,也可以選擇編寫出更好的程序,讓你的系統(tǒng)可以處理更高的負(fù)載。這樣做可以節(jié)省更多的硬件開(kāi)支,并讓你的整個(gè)應(yīng)用更加可靠,具有更快的響應(yīng)時(shí)間。對(duì)于程序員來(lái)說(shuō),這必將獲得非常大的滿足感。
大量的對(duì)象
當(dāng)系統(tǒng)中的對(duì)象數(shù)量增長(zhǎng)到一定程度,我們通常將面臨規(guī)模問(wèn)題。隨著對(duì)象數(shù)目的增長(zhǎng),所有類型的資源開(kāi)銷顯然會(huì)帶來(lái)很大的壓力。
持續(xù)的故障會(huì)產(chǎn)生一個(gè)無(wú)窮事件流
在大型網(wǎng)絡(luò)的故障場(chǎng)景中,我們沒(méi)有時(shí)間進(jìn)行系統(tǒng)恢復(fù)。因?yàn)槲覀兪冀K處于一個(gè)持續(xù)的壓力狀態(tài)下。
大量的高優(yōu)先級(jí)任務(wù)
舉個(gè)例子,重選路由(rerouting)是個(gè)高優(yōu)先級(jí)的活動(dòng)。如果存在大量不能分發(fā)(shed)或取消(squelched)的重選路由任務(wù),資源將連續(xù)不斷的被消耗以支持高優(yōu)先級(jí)任務(wù)。
更大的數(shù)據(jù)流
當(dāng)媒體資源大小增長(zhǎng)得更大時(shí),系統(tǒng)的負(fù)載將會(huì)增長(zhǎng)。系統(tǒng)負(fù)載會(huì)隨著請(qǐng)求來(lái)源數(shù)目的增長(zhǎng)而增長(zhǎng)。
功能變更
在系統(tǒng)原有設(shè)計(jì)之外添加更多的功能后,系統(tǒng)中的漏洞也隨之暴露。
客戶端的增長(zhǎng)
更多的客戶端意味著更多的資源消耗。需要設(shè)置線程為客戶端推送事件;客戶端隊(duì)列會(huì)消耗內(nèi)存;通信會(huì)消耗帶寬;需要為每個(gè)客戶端維持一份獨(dú)立數(shù)據(jù)。
不夠好的設(shè)計(jì)決策
以下是一些會(huì)加劇規(guī)模問(wèn)題的設(shè)計(jì)問(wèn)題。
- 設(shè)計(jì)未考慮處理大量對(duì)象。
- 缺乏端到端應(yīng)用級(jí)別的流控。
- 應(yīng)用級(jí)別的重試將導(dǎo)致資源再分配和消息發(fā)送。
- 內(nèi)存中代碼庫(kù)的開(kāi)銷。
- 并不真正可靠的發(fā)布。
- 特定數(shù)據(jù)結(jié)構(gòu)過(guò)度占用內(nèi)存。
- 消息協(xié)議沒(méi)有處理所有故障場(chǎng)景。
- 使用磁盤作為存儲(chǔ)。
- 磁盤同步?jīng)]有使用塊復(fù)制。
- 認(rèn)為應(yīng)用層協(xié)議會(huì)更有好。
- 依靠大功耗的CPU來(lái)提升功能負(fù)載。
- 操作系統(tǒng)不支持進(jìn)程架構(gòu)。(譯者注:該點(diǎn)討論見(jiàn)原文鏈接處的評(píng)論)
- 缺乏對(duì)于某些敏感操作(甚至是單條消息的刪除)的硬件支持。
- 特別的網(wǎng)絡(luò)問(wèn)題,比如ARP數(shù)據(jù)包,當(dāng)網(wǎng)絡(luò)負(fù)載增長(zhǎng)時(shí)就會(huì)出現(xiàn)丟失。
無(wú)效的假設(shè)
你所做的大部分假設(shè)都是無(wú)效的,比如你需要使用多少內(nèi)存,某些任務(wù)需要運(yùn)行多少時(shí)間,設(shè)置多少超時(shí)時(shí)間才算合理,一次會(huì)消費(fèi)多少資源,可能發(fā)生什么樣的故障,系統(tǒng)在不同點(diǎn)的延遲,你的隊(duì)列需要多大,等等。
內(nèi)存不足
內(nèi)存使用基線的增長(zhǎng)和內(nèi)存使用峰值的增長(zhǎng),都會(huì)導(dǎo)致內(nèi)存不足。
CPU饑餓
隨著對(duì)象操作的增加,將需要占用更長(zhǎng)的時(shí)間,這是因?yàn)檫@些操作需要操作更多的對(duì)象??捎肅PU將變得更少?gòu)亩鴮?dǎo)致其他的操作將面臨CPU饑餓。系統(tǒng)一處出現(xiàn)了饑餓,就會(huì)傳播到其他各處。這樣一來(lái)就沒(méi)有足夠的CPU來(lái)處理那些需要去完成的必要任務(wù)。導(dǎo)致這樣的原因可能是任務(wù)的基數(shù)過(guò)高或某些情況下很多高優(yōu)先級(jí)的任務(wù)需要完成。
原始資源使用率增長(zhǎng)
更多的對(duì)象將占用更多的內(nèi)存。如果某人想要支持1000個(gè)并發(fā)對(duì)象,很可能是做不到的,因?yàn)槟憧赡芨揪蜎](méi)有足夠的內(nèi)存。
隱式資源使用率增長(zhǎng)
大多數(shù)功能中,針對(duì)“原始”資源使用開(kāi)銷中的每項(xiàng)資源,都將會(huì)需要大量的額外資源。如果你將一個(gè)對(duì)象存儲(chǔ)在兩個(gè)不同的列表里,你的對(duì)象數(shù)目和內(nèi)存開(kāi)銷將是原來(lái)的兩倍;隊(duì)列大小可能也需要向上調(diào)整;磁盤的數(shù)目需要增加;將數(shù)據(jù)復(fù)制到從機(jī)的時(shí)間在增長(zhǎng);將數(shù)據(jù)加載到應(yīng)用中的時(shí)間在增長(zhǎng);為了處理這些工作CPU的使用率在增長(zhǎng);啟動(dòng)時(shí)間也在增長(zhǎng)。
事件的疊加
很多系統(tǒng)面臨的一個(gè)新的現(xiàn)實(shí)是無(wú)窮工作流。Web服務(wù)器和應(yīng)用服務(wù)器服務(wù)著非常大的用戶群,這是一個(gè)真實(shí)的可預(yù)計(jì)的關(guān)于新工作的無(wú)窮流。而工作將永不結(jié)束。一周7天24小時(shí)都會(huì)有請(qǐng)求進(jìn)來(lái)。以100%的CPU使用率進(jìn)行工作可以很容易使服務(wù)器達(dá)到飽和。
習(xí)慣上我們將100%的CPU使用率視為一個(gè)不良的信號(hào)。作為補(bǔ)償,我們將創(chuàng)建復(fù)雜的基礎(chǔ)設(shè)施來(lái)對(duì)工作進(jìn)行負(fù)載均衡,復(fù)制狀態(tài)并做好主機(jī)集群。
CPU永不疲倦,所以你可能會(huì)認(rèn)為我們應(yīng)該嘗試使用盡可能多的CPU。
在其他領(lǐng)域我們?cè)噲D通過(guò)最大限度的利用資源來(lái)提升生產(chǎn)率。
而在服務(wù)器的世界里,我們嘗試通過(guò)人為的方式強(qiáng)制地降低CPU使用率從而保證一定的響應(yīng)水平。該理由是因?yàn)槿绻覀儧](méi)有更高的CPU可用性,我們將無(wú)法在合理的延遲時(shí)間內(nèi)響應(yīng)新的工作或完成現(xiàn)有的工作。
CPU使用率達(dá)到100%真的存在問(wèn)題嗎?我們?cè)趯?duì)系統(tǒng)做架構(gòu)設(shè)計(jì)時(shí),寧可使用CPU可用性和任務(wù)優(yōu)先級(jí)作為架構(gòu)設(shè)計(jì)認(rèn)知上簡(jiǎn)略的一種解釋依據(jù),也不愿意先去理解下我們系統(tǒng)的底層工作流,從而使用這些信息再來(lái)做出明確的規(guī)劃決策。難道這不正是真正的問(wèn)題所在嗎?
我們基于負(fù)載均衡服務(wù)器做出了拙劣的架構(gòu)決策,通過(guò)主觀臆想猜測(cè)了使用的線程數(shù)以及這些線程的優(yōu)先級(jí)。除了以上這些,我們根本沒(méi)有使用工具做過(guò)任何其他事。
擴(kuò)展一個(gè)系統(tǒng)需要仔細(xì)地關(guān)注架構(gòu)。在當(dāng)前框架的應(yīng)用程序中,卻很少有對(duì)所轄?wèi)?yīng)用是如何運(yùn)行的進(jìn)行說(shuō)明。
延遲的增長(zhǎng)
你所經(jīng)歷的延遲增長(zhǎng)與規(guī)模增長(zhǎng)可能是完全不同的。CPU饑餓是該問(wèn)題的主要原因。
任務(wù)優(yōu)先級(jí)被證明是錯(cuò)誤的
任務(wù)優(yōu)先級(jí)方案可以有效地工作在較小負(fù)載下,但是在高負(fù)載下就會(huì)產(chǎn)生問(wèn)題。舉個(gè)典型的例子,有一套簡(jiǎn)陋的流控裝置,一個(gè)高優(yōu)先級(jí)任務(wù)向一個(gè)較低優(yōu)先級(jí)的任務(wù)傳遞工作將會(huì)導(dǎo)致工作丟失和內(nèi)存使用峰值,因?yàn)榈蛢?yōu)先級(jí)的任務(wù)的運(yùn)行機(jī)會(huì)將會(huì)很少。
隊(duì)列容量不足夠大
大量的對(duì)象意味著可以進(jìn)行更多的并發(fā)操作,這意味著隊(duì)列容量將很可能需要擴(kuò)充。
啟動(dòng)時(shí)間更久
更多的對(duì)象需要更久的時(shí)間才能將它們從磁盤加載到應(yīng)用中。
同步時(shí)間更久
需要更多的時(shí)間才能將更多的對(duì)象在應(yīng)用之間進(jìn)行同步。
在大型配置中沒(méi)有進(jìn)行足夠的測(cè)試
因?yàn)闇y(cè)試裝置成本很高,所以我們實(shí)際花費(fèi)在大型設(shè)置上的測(cè)試時(shí)間非常少。你在開(kāi)發(fā)期間不需要接觸大型系統(tǒng),所以很可能你的設(shè)計(jì)一開(kāi)始就不能支持大規(guī)模的場(chǎng)景。
操作耗時(shí)更久
如果一個(gè)操作作用于每個(gè)對(duì)象,當(dāng)更多的對(duì)象被添加進(jìn)來(lái)后,該操作的耗時(shí)也將會(huì)更久。當(dāng)數(shù)據(jù)表變得更大時(shí),過(guò)去針對(duì)一定數(shù)據(jù)量足夠快速的查詢,如今的耗時(shí)也會(huì)大幅度的增長(zhǎng)。
更多的隨機(jī)故障
在正常操作中你可能不會(huì)看到某些故障。但在一定規(guī)模下,響應(yīng)將丟失,ARP請(qǐng)求將丟失,文件系統(tǒng)可能會(huì)出現(xiàn)某些錯(cuò)誤,消息可能丟失,回復(fù)也可能丟失,等等。
更大的故障窗口
規(guī)模化導(dǎo)致每個(gè)環(huán)節(jié)都會(huì)耗時(shí)更久,這意味著出現(xiàn)故障的概率就會(huì)更大。一個(gè)數(shù)據(jù)交換協(xié)議在處理少量數(shù)據(jù)集的時(shí)候會(huì)很快,這意味著它只有很小的機(jī)會(huì)遭遇重啟或超時(shí)。但是更大的規(guī)模中,故障窗口將擴(kuò)大從而第一之間就會(huì)遭遇到新問(wèn)題。
沒(méi)有提高超時(shí)設(shè)置
任何超時(shí)在較小的數(shù)據(jù)集中可以有效工作,但是當(dāng)數(shù)據(jù)集逐漸增長(zhǎng)后將不再適用。就CPU饑餓問(wèn)題而言,你所編寫的代碼可能還沒(méi)來(lái)得及跑,超時(shí)時(shí)間卻早已達(dá)到。
沒(méi)有增加重試次數(shù)
沒(méi)有辦法在確定故障之前為某應(yīng)用指定重試次數(shù),因?yàn)樗麄儧](méi)有這方面足夠的信息來(lái)支持決策。每秒4次重試是否合理?為什么不是20次呢?
優(yōu)先級(jí)繼承
更久的持有大范圍的鎖將有更好的機(jī)會(huì)遭遇優(yōu)先級(jí)繼承問(wèn)題。
消費(fèi)模式的打破
在一種規(guī)模下你可以從生產(chǎn)者獲取所有數(shù)據(jù),但是在另一規(guī)模下你將會(huì)耗盡隊(duì)列的空間或內(nèi)存。舉個(gè)例子:某個(gè)輪詢程序在將數(shù)據(jù)傳遞到下一個(gè)隊(duì)列之前,會(huì)一直向遠(yuǎn)程隊(duì)列輪詢所有的數(shù)據(jù)源。當(dāng)隊(duì)列中只有很少量數(shù)據(jù)項(xiàng)的時(shí)候該程序可以有效的運(yùn)行。但是很可能因?yàn)槟硞€(gè)功能的變更擴(kuò)大了該遠(yuǎn)程隊(duì)列中的數(shù)據(jù)項(xiàng)數(shù)量,這樣一來(lái)該輪詢程序?qū)?huì)導(dǎo)致某個(gè)節(jié)點(diǎn)內(nèi)存不足。
監(jiān)控器超時(shí)
100%CPU的情況將導(dǎo)致監(jiān)控器超時(shí)。這在小規(guī)模系統(tǒng)中很少發(fā)生,但是在設(shè)計(jì)不良的較大型規(guī)模系統(tǒng)中就會(huì)發(fā)生。
慢速的內(nèi)存泄露變成快速泄露
較小規(guī)模系統(tǒng)中不大引人注意的一個(gè)內(nèi)存泄露問(wèn)題在較大規(guī)模的系統(tǒng)中就變得影響重大。
原本未注意到的鎖問(wèn)題變得引人注目
應(yīng)該在適當(dāng)?shù)牡胤绞褂面i。但是如果使用不當(dāng),該問(wèn)題在較小規(guī)模的系統(tǒng)中也許會(huì)被人忽略。因?yàn)槌钟墟i的線程會(huì)在另一段產(chǎn)生問(wèn)題的指令運(yùn)行前釋放掉它長(zhǎng)期占用的CPU使用權(quán)。但是在大規(guī)模系統(tǒng)中將會(huì)有更多的CPU搶占,這意味著將會(huì)有更多的機(jī)會(huì)看到不同線程對(duì)同一數(shù)據(jù)的并發(fā)訪問(wèn)。
死鎖的機(jī)會(huì)變大
不同的調(diào)度模式將以不同的路徑運(yùn)行代碼,所以遭遇死鎖的機(jī)會(huì)也就更大。舉個(gè)例子,當(dāng)CPU使用率很高時(shí)文件系統(tǒng)沒(méi)有機(jī)會(huì)得到運(yùn)行,而當(dāng)以某種方式打破這一情形時(shí),文件系統(tǒng)隨即占用了100%的CPU使用權(quán)卻再也沒(méi)有運(yùn)行。
時(shí)間同步變?cè)?/strong>
時(shí)間同步任務(wù)的優(yōu)先級(jí)并不高,所以當(dāng)可用的CPU和網(wǎng)絡(luò)資源變得更少時(shí),不同節(jié)點(diǎn)的時(shí)鐘將出現(xiàn)偏差。
日志數(shù)據(jù)丟失
由于日志隊(duì)列容量過(guò)小從而無(wú)法應(yīng)對(duì)增長(zhǎng)的負(fù)載或是因?yàn)镃PU太忙而沒(méi)有時(shí)間片給予日志記錄器分發(fā)日志數(shù)據(jù),都將可能導(dǎo)致日志記錄器開(kāi)始丟失數(shù)據(jù)。根據(jù)隊(duì)列的容量和類型不同,或?qū)?dǎo)致內(nèi)存不足。
定時(shí)器沒(méi)有在準(zhǔn)確的時(shí)間觸發(fā)
一個(gè)繁忙的系統(tǒng)無(wú)法在期望的時(shí)間觸發(fā)定時(shí)器,這將導(dǎo)致系統(tǒng)的其余部分出現(xiàn)一連串的延遲。
ARP數(shù)據(jù)包丟失
在高負(fù)載的CPU或網(wǎng)絡(luò)環(huán)境中,在主機(jī)間傳送的ARP數(shù)據(jù)包可能會(huì)丟失。這是因?yàn)閿?shù)據(jù)包被發(fā)送到了錯(cuò)誤的網(wǎng)卡,一旦更新完路由表,將不會(huì)再發(fā)生這種情況。
文件描述符限制
在一個(gè)硬件上通常都會(huì)有一個(gè)固定的文件描述符數(shù)量上限。系統(tǒng)設(shè)計(jì)必須將所需的最大文件描述符數(shù)量限制在該上限以內(nèi)。如果取用的套接字描述符超過(guò)了文件描述符的可用池,那么涉及到大量連接(ftp,com,啟動(dòng),客戶端等等)的設(shè)計(jì)將會(huì)產(chǎn)生問(wèn)題。規(guī)?;瘜⒖赡茉斐擅枋龇枨髷?shù)量的峰值。當(dāng)規(guī)模增長(zhǎng)時(shí),描述符泄露將會(huì)耗盡可用池。
套接字緩沖限制
系統(tǒng)都會(huì)為每個(gè)套接字分配一定量的緩沖空間,大量的套接字可能會(huì)減少系統(tǒng)整體的可用內(nèi)存。隨著規(guī)模增長(zhǎng),消息丟失也開(kāi)始增長(zhǎng)。這是因?yàn)榻邮障⒌木彌_空間數(shù)量不足從而跟不上負(fù)載的壓力。這同樣也和優(yōu)先級(jí)相關(guān),因?yàn)橐粋€(gè)任務(wù)沒(méi)有足夠的優(yōu)先級(jí)從套接字中讀取數(shù)據(jù)。較低優(yōu)先級(jí)的任務(wù)可能會(huì)被發(fā)送者一方某個(gè)高優(yōu)先級(jí)任務(wù)的消息所淹沒(méi)。
啟動(dòng)鏡像服務(wù)限制
一個(gè)節(jié)點(diǎn)的啟動(dòng)卡同一時(shí)間可服務(wù)的限制為X。FTP服務(wù)器基礎(chǔ)設(shè)施必須限制啟動(dòng)卡服務(wù)的數(shù)量,否則將造成該節(jié)點(diǎn)發(fā)生CPU饑餓。
消息次序混亂
你的消息系統(tǒng)在高負(fù)載壓力下傳遞消息的次數(shù)可能會(huì)發(fā)生混亂,這對(duì)非冪等的操作來(lái)說(shuō)將產(chǎn)生問(wèn)題。
協(xié)議的弱點(diǎn)
除非小心謹(jǐn)慎的創(chuàng)建應(yīng)用層協(xié)議,否則規(guī)模的增長(zhǎng)將帶來(lái)大量的問(wèn)題。
連接限制
一個(gè)某種類型的中央服務(wù)器在應(yīng)付十個(gè)客戶端的情況下也許綽綽有余。但是當(dāng)有一千客戶端的時(shí)候,它將無(wú)法滿足到響應(yīng)時(shí)間的需求。在這種情況下,平均響應(yīng)時(shí)間將根據(jù)客戶端數(shù)量成線性增長(zhǎng),我們稱該復(fù)雜度為O(N)(“order N”),但是若是其他更差的復(fù)雜度將會(huì)產(chǎn)生問(wèn)題。舉個(gè)例子,我們希望一個(gè)網(wǎng)絡(luò)中的N個(gè)節(jié)點(diǎn)可以互相通信,我們可以讓每個(gè)節(jié)點(diǎn)鏈接到一臺(tái)中央交換服務(wù)器,這將需要O(N)條連接線?;蛘呶覀?cè)诿績(jī)蓚€(gè)節(jié)點(diǎn)之間直接建立連接,這將需要O(N^2)條連接線(確切的數(shù)字或公式通常不重要,這只跟涉及到的N的最高次有關(guān))。
分層架構(gòu)
這是一個(gè)很好的總結(jié),所以我在此處引用了它:基于分層的架構(gòu)從來(lái)就不是用來(lái)構(gòu)建低延遲,高吞吐量應(yīng)用的。對(duì)于多層架構(gòu),究其本質(zhì)是被創(chuàng)建用于解決昨天的歷史問(wèn)題的。從客戶端-服務(wù)器時(shí)代過(guò)渡到互聯(lián)網(wǎng)時(shí)代,它是可伸縮性方面最完美的解決方案。
該問(wèn)題域是關(guān)于如何擴(kuò)展應(yīng)用的規(guī)模以支持成百上千的用戶。然而今天的我們都知道對(duì)于該問(wèn)題的解決方案就是n層架構(gòu)。在可伸縮性的維度上我們選擇了通過(guò)負(fù)載均衡的表現(xiàn)層,事實(shí)上這的確解決了問(wèn)題。然而,當(dāng)今時(shí)代,問(wèn)題發(fā)生了演變。這些日子里,很多行業(yè)的問(wèn)題已不再是僅僅關(guān)于提升用戶體驗(yàn)了,數(shù)據(jù)量也成為了一個(gè)問(wèn)題。
多處理器性能問(wèn)題
當(dāng)處理器被要求在大量無(wú)關(guān)工作間切換時(shí),原本強(qiáng)大的硬件緩存加速常常會(huì)失效。
查看英文原文:42 Monster Problems That Attack As Loads Increase
解決方案可參看:抵御負(fù)載怪獸攻擊,確??缮炜s性的7條秘訣
感謝楊賽對(duì)本文的審校。