系統負載增加時將會遭遇的42個怪獸問題

infoq發表於2013-05-13

  解決方案可參看:抵禦負載怪獸攻擊,確保可伸縮性的7條祕訣

  隨著負載的增長,你精心設計的程式可能會遭遇很多突如其來的問題:系統原有的平穩將被打破,我們將對這些問題逐一考察。當然,你可以進行橫向或縱向的擴充套件,也可以選擇編寫出更好的程式,讓你的系統可以處理更高的負載。這樣做可以節省更多的硬體開支,並讓你的整個應用更加可靠,具有更快的響應時間。對於程式設計師來說,這必將獲得非常大的滿足感。

  大量的物件

  當系統中的物件數量增長到一定程度,我們通常將面臨規模問題。隨著物件數目的增長,所有型別的資源開銷顯然會帶來很大的壓力。

  持續的故障會產生一個無窮事件流

  在大型網路的故障場景中,我們沒有時間進行系統恢復。因為我們始終處於一個持續的壓力狀態下。

  大量的高優先順序任務

  舉個例子,重選路由(rerouting)是個高優先順序的活動。如果存在大量不能分發(shed)或取消(squelched)的重選路由任務,資源將連續不斷的被消耗以支援高優先順序任務。

  更大的資料流

  當媒體資源大小增長得更大時,系統的負載將會增長。系統負載會隨著請求來源數目的增長而增長。

  功能變更

  在系統原有設計之外新增更多的功能後,系統中的漏洞也隨之暴露。

  客戶端的增長

  更多的客戶端意味著更多的資源消耗。需要設定執行緒為客戶端推送事件;客戶端佇列會消耗記憶體;通訊會消耗頻寬;需要為每個客戶端維持一份獨立資料。

  不夠好的設計決策

  以下是一些會加劇規模問題的設計問題。

  • 設計未考慮處理大量物件。
  • 缺乏端到端應用級別的流控。
  • 應用級別的重試將導致資源再分配和訊息傳送。
  • 記憶體中程式碼庫的開銷。
  • 並不真正可靠的釋出。
  • 特定資料結構過度佔用記憶體。
  • 訊息協議沒有處理所有故障場景。
  • 使用磁碟作為儲存。
  • 磁碟同步沒有使用塊複製。
  • 認為應用層協議會更有好。
  • 依靠大功耗的CPU來提升功能負載。
  • 作業系統不支援程式架構。(譯者注:該點討論見原文連結處的評論
  • 缺乏對於某些敏感操作(甚至是單條訊息的刪除)的硬體支援。
  • 特別的網路問題,比如ARP資料包,當網路負載增長時就會出現丟失。

  無效的假設

  你所做的大部分假設都是無效的,比如你需要使用多少記憶體,某些任務需要執行多少時間,設定多少超時時間才算合理,一次會消費多少資源,可能發生什麼樣的故障,系統在不同點的延遲,你的佇列需要多大,等等。

  記憶體不足

  記憶體使用基線的增長和記憶體使用峰值的增長,都會導致記憶體不足。

  CPU飢餓

  隨著物件操作的增加,將需要佔用更長的時間,這是因為這些操作需要操作更多的物件。可用CPU將變得更少從而導致其他的操作將面臨CPU飢餓。系統一處出現了飢餓,就會傳播到其他各處。這樣一來就沒有足夠的CPU來處理那些需要去完成的必要任務。導致這樣的原因可能是任務的基數過高或某些情況下很多高優先順序的任務需要完成。

  原始資源使用率增長

  更多的物件將佔用更多的記憶體。如果某人想要支援1000個併發物件,很可能是做不到的,因為你可能根本就沒有足夠的記憶體。

  隱式資源使用率增長

  大多數功能中,針對“原始”資源使用開銷中的每項資源,都將會需要大量的額外資源。如果你將一個物件儲存在兩個不同的列表裡,你的物件數目和記憶體開銷將是原來的兩倍;佇列大小可能也需要向上調整;磁碟的數目需要增加;將資料複製到從機的時間在增長;將資料載入到應用中的時間在增長;為了處理這些工作CPU的使用率在增長;啟動時間也在增長。

  事件的疊加

  很多系統面臨的一個新的現實是無窮工作流。Web伺服器和應用伺服器服務著非常大的使用者群,這是一個真實的可預計的關於新工作的無窮流。而工作將永不結束。一週7天24小時都會有請求進來。以100%的CPU使用率進行工作可以很容易使伺服器達到飽和。

  習慣上我們將100%的CPU使用率視為一個不良的訊號。作為補償,我們將建立複雜的基礎設施來對工作進行負載均衡,複製狀態並做好主機叢集。

  CPU永不疲倦,所以你可能會認為我們應該嘗試使用盡可能多的CPU。

  在其他領域我們試圖通過最大限度的利用資源來提升生產率。

  而在伺服器的世界裡,我們嘗試通過人為的方式強制地降低CPU使用率從而保證一定的響應水平。該理由是因為如果我們沒有更高的CPU可用性,我們將無法在合理的延遲時間內響應新的工作或完成現有的工作。

  CPU使用率達到100%真的存在問題嗎?我們在對系統做架構設計時,寧可使用CPU可用性和任務優先順序作為架構設計認知上簡略的一種解釋依據,也不願意先去理解下我們系統的底層工作流,從而使用這些資訊再來做出明確的規劃決策。難道這不正是真正的問題所在嗎?

  我們基於負載均衡伺服器做出了拙劣的架構決策,通過主觀臆想猜測了使用的執行緒數以及這些執行緒的優先順序。除了以上這些,我們根本沒有使用工具做過任何其他事。

  擴充套件一個系統需要仔細地關注架構。在當前框架的應用程式中,卻很少有對所轄應用是如何執行的進行說明。

  延遲的增長

  你所經歷的延遲增長與規模增長可能是完全不同的。CPU飢餓是該問題的主要原因。

  任務優先順序被證明是錯誤的

  任務優先順序方案可以有效地工作在較小負載下,但是在高負載下就會產生問題。舉個典型的例子,有一套簡陋的流控裝置,一個高優先順序任務向一個較低優先順序的任務傳遞工作將會導致工作丟失和記憶體使用峰值,因為低優先順序的任務的執行機會將會很少。

  佇列容量不足夠大

  大量的物件意味著可以進行更多的併發操作,這意味著佇列容量將很可能需要擴充。

  啟動時間更久

  更多的物件需要更久的時間才能將它們從磁碟載入到應用中。

  同步時間更久

  需要更多的時間才能將更多的物件在應用之間進行同步。

  在大型配置中沒有進行足夠的測試

  因為測試裝置成本很高,所以我們實際花費在大型設定上的測試時間非常少。你在開發期間不需要接觸大型系統,所以很可能你的設計一開始就不能支援大規模的場景。

  操作耗時更久

  如果一個操作作用於每個物件,當更多的物件被新增進來後,該操作的耗時也將會更久。當資料表變得更大時,過去針對一定資料量足夠快速的查詢,如今的耗時也會大幅度的增長。

  更多的隨機故障

  在正常操作中你可能不會看到某些故障。但在一定規模下,響應將丟失,ARP請求將丟失,檔案系統可能會出現某些錯誤,訊息可能丟失,回覆也可能丟失,等等。

  更大的故障視窗

  規模化導致每個環節都會耗時更久,這意味著出現故障的概率就會更大。一個資料交換協議在處理少量資料集的時候會很快,這意味著它只有很小的機會遭遇重啟或超時。但是更大的規模中,故障視窗將擴大從而第一之間就會遭遇到新問題。

  沒有提高超時設定

  任何超時在較小的資料集中可以有效工作,但是當資料集逐漸增長後將不再適用。就CPU飢餓問題而言,你所編寫的程式碼可能還沒來得及跑,超時時間卻早已達到。

  沒有增加重試次數

  沒有辦法在確定故障之前為某應用指定重試次數,因為他們沒有這方面足夠的資訊來支援決策。每秒4次重試是否合理?為什麼不是20次呢?

  優先順序繼承

  更久的持有大範圍的鎖將有更好的機會遭遇優先順序繼承問題。

  消費模式的打破

  在一種規模下你可以從生產者獲取所有資料,但是在另一規模下你將會耗盡佇列的空間或記憶體。舉個例子:某個輪詢程式在將資料傳遞到下一個佇列之前,會一直向遠端佇列輪詢所有的資料來源。當佇列中只有很少量資料項的時候該程式可以有效的執行。但是很可能因為某個功能的變更擴大了該遠端佇列中的資料項數量,這樣一來該輪詢程式將會導致某個節點記憶體不足。

  監控器超時

  100%CPU的情況將導致監控器超時。這在小規模系統中很少發生,但是在設計不良的較大型規模系統中就會發生。

  慢速的記憶體洩露變成快速洩露

  較小規模系統中不大引人注意的一個記憶體洩露問題在較大規模的系統中就變得影響重大。

  原本未注意到的鎖問題變得引人注目

  應該在適當的地方使用鎖。但是如果使用不當,該問題在較小規模的系統中也許會被人忽略。因為持有鎖的執行緒會在另一段產生問題的指令執行前釋放掉它長期佔用的CPU使用權。但是在大規模系統中將會有更多的CPU搶佔,這意味著將會有更多的機會看到不同執行緒對同一資料的併發訪問。

  死鎖的機會變大

  不同的排程模式將以不同的路徑執行程式碼,所以遭遇死鎖的機會也就更大。舉個例子,當CPU使用率很高時檔案系統沒有機會得到執行,而當以某種方式打破這一情形時,檔案系統隨即佔用了100%的CPU使用權卻再也沒有執行。

  時間同步變糟

  時間同步任務的優先順序並不高,所以當可用的CPU和網路資源變得更少時,不同節點的時鐘將出現偏差。

  日誌資料丟失

  由於日誌佇列容量過小從而無法應對增長的負載或是因為CPU太忙而沒有時間片給予日誌記錄器分發日誌資料,都將可能導致日誌記錄器開始丟失資料。根據佇列的容量和型別不同,或將導致記憶體不足。

  定時器沒有在準確的時間觸發

  一個繁忙的系統無法在期望的時間觸發定時器,這將導致系統的其餘部分出現一連串的延遲。

  ARP資料包丟失

  在高負載的CPU或網路環境中,在主機間傳送的ARP資料包可能會丟失。這是因為資料包被髮送到了錯誤的網路卡,一旦更新完路由表,將不會再發生這種情況。

  檔案描述符限制

  在一個硬體上通常都會有一個固定的檔案描述符數量上限。系統設計必須將所需的最大檔案描述符數量限制在該上限以內。如果取用的套接字描述符超過了檔案描述符的可用池,那麼涉及到大量連線(ftp,com,啟動,客戶端等等)的設計將會產生問題。規模化將可能造成描述符需求數量的峰值。當規模增長時,描述符洩露將會耗盡可用池。

  套接字緩衝限制

  系統都會為每個套接字分配一定量的緩衝空間,大量的套接字可能會減少系統整體的可用記憶體。隨著規模增長,訊息丟失也開始增長。這是因為接收訊息的緩衝空間數量不足從而跟不上負載的壓力。這同樣也和優先順序相關,因為一個任務沒有足夠的優先順序從套接字中讀取資料。較低優先順序的任務可能會被髮送者一方某個高優先順序任務的訊息所淹沒。

  啟動映象服務限制

  一個節點的啟動卡同一時間可服務的限制為X。FTP伺服器基礎設施必須限制啟動卡服務的數量,否則將造成該節點發生CPU飢餓。

  訊息次序混亂

  你的訊息系統在高負載壓力下傳遞訊息的次數可能會發生混亂,這對非冪等的操作來說將產生問題。

  協議的弱點

  除非小心謹慎的建立應用層協議,否則規模的增長將帶來大量的問題。

  連線限制

  一個某種型別的中央伺服器在應付十個客戶端的情況下也許綽綽有餘。但是當有一千客戶端的時候,它將無法滿足到響應時間的需求。在這種情況下,平均響應時間將根據客戶端數量成線性增長,我們稱該複雜度為O(N)(“order N”),但是若是其他更差的複雜度將會產生問題。舉個例子,我們希望一個網路中的N個節點可以互相通訊,我們可以讓每個節點連結到一臺中央交換伺服器,這將需要O(N)條連線線。或者我們在每兩個節點之間直接建立連線,這將需要O(N^2)條連線線(確切的數字或公式通常不重要,這隻跟涉及到的N的最高次有關)。

  分層架構

  這是一個很好的總結,所以我在此處引用了它:基於分層的架構從來就不是用來構建低延遲,高吞吐量應用的。對於多層架構,究其本質是被建立用於解決昨天的歷史問題的。從客戶端-伺服器時代過渡到網際網路時代,它是可伸縮性方面最完美的解決方案。

  該問題域是關於如何擴充套件應用的規模以支援成百上千的使用者。然而今天的我們都知道對於該問題的解決方案就是n層架構。在可伸縮性的維度上我們選擇了通過負載均衡的表現層,事實上這的確解決了問題。然而,當今時代,問題發生了演變。這些日子裡,很多行業的問題已不再是僅僅關於提升使用者體驗了,資料量也成為了一個問題。

  多處理器效能問題

  當處理器被要求在大量無關工作間切換時,原本強大的硬體快取加速常常會失效。

  英文原文:42 Monster Problems That Attack As Loads Increase

相關文章