英文原文:williamhertling,翻譯:夏夢竹@CSDN
導讀:作者William Hertling的業餘愛好是寫科幻小說,目前就職於HP。他在部落格中談到了如何在三天內讓一個Web應用程式承載擴充1,000x的實時併發訪問量。對此他分享了自己的經驗,包括怎麼做到、從中學到了什麼,以及從中吸取的經驗。
環境:由NgniX,Ruby on Rails和MySQL構成。注:這個Web應用只是一個旅行指南。
當使用者進入我們的網站時,會通過TripIt導航或者選定目標,再依據詳細資訊進行下面的操作。他們可以根據自己的喜好選擇不同類別(比如餐廳的菜式風格、評級指標等吸引點進行篩選)。或者也可以通過瀏覽器檢視生活指南做一些預訂。
儘管我們的網站還處於beta版中,但是每天仍有幾百萬的訪問量。週一下午2點我們接到一則通知,網站將在週四早上9:30(東部時間)一個非常受歡迎的電視節目上出現,根據以往的經驗,我們料想這一天將會有20,000至150,000的需求訪量,峰值可達到10,000,而伺服器將承受100x至1,000x的需求請求數。
我們估計將會有10,000人同時訪問,也就意味著要建立每分鐘約40,000頁的請求數。
我們所具備的:
▲執行於Amazon Web Services,採用EC2雲平臺。
▲當CPU負載超過網站伺服器端時採用auto-scale規則檢測,EC2例項將增加一倍。
▲部署過程(儘管這成為一種約束)
▲一個月前,花了一週時間將網站效能進行優化,將網頁載入時間縮減了30%,大部分是通過減少資料庫呼叫。
週一,我們在幾臺電腦上安裝了JMeter(JMeter是Apache組織的開放原始碼專案,它是功能和效能測試的工具)。你可以使用JMeter編寫指令碼從而模擬一個典型的使用者訪問。在以往案例中,會通過指令碼來載入首頁,通過選定目標,選擇自己的喜好進入瀏覽,但我們沒有發表任何留言(如果時間充足的話,我們很樂意去做)。利用模擬使用者將大部分的資料庫資料來構建使用者介面。(這一點很重要)。
經過幾次試驗,我們發現當執行緒超過125個時,就無法在單一的JMeter例項上執行了。因此,如果我們要模擬真正的高負荷,這需要將JMeter同時執行在幾臺電腦上。同時,我們還要確定本地網路不在飽和狀態,登陸VPN進入另一個網路,由一個客戶端切換到另一個遠端客戶端。
我們很快了解到:
▲需要改變最初的auto-scale團隊由2臺伺服器變成4臺;
▲需要將CPU執行載入由80%降至50%
▲需要將載入持續的時間由5分鐘減少至1分鐘
經過這些改變後,讓應用服務端規模擴大至16臺,但這受到資料庫的約束。資料庫很快達到100%負荷,儘管我們擴大了AWS(Amazon Web Services)資料庫的規模(超大的CPU和記憶體)但是仍然受約束。於是我們採用JMeter執行測試,設定平均響應時間為30秒,我們清楚的知道,如果這個問題無法解決,那我們便失敗了。這一天是星期二。
我說過“我們會失敗”,此前,我們採取了很多解決措施,團隊中的大部分成員認為這樣做未免太冒險了,會釀成大錯。因為根據以往100%正確經驗:在高負荷載入情況下會導致頁面無法顯示,這是100%有風險的。經過幾個小時深思熟慮之後,我們深信,必須要解決可擴充套件性問題。
我們簡要的探討了幾個不同的且動作較大的方法,但這些風險很大:
▲複製整個堆疊,並將他們之間的執行緒改為使用DynDNS或者Amazon’s Route 53,因為我們沒有使用者賬戶或者是其他的共享資料,因此只能這麼做。當然,我們也可以通過指令碼將所有使用者資料收集在一起來同步到所需資料庫。但前提是,如果我們有一週的時間或者有(Ops)專業人士,或許我們會採取此方案。
▲解除安裝資料庫載入的一部分,通過建立一個只讀副本,分散資料庫呼叫主要是靜態資料(如目的地或者那些目的地中有吸引力的部分)來作為只讀資料。我們並沒有採取此做法,主要是因為在一臺資料庫伺服器和其他不同資料庫伺服器之間無法找到資料的需求點。
▲使用一種機制memcache或是其他(mechanisms)機制用只讀儲存器取代資料庫,這將導致大量的程式碼被更改。由於時間太短,我們認為風險很大。
因此,我們決定採取行動:
我們將一些易於識別的DB需求執行在每個頁面上,這樣能夠滿足每次載入相同的資料。我們在儲存器中重新編寫程式碼進行快取,所以,當伺服器重新啟動載入時,不會重複。
週三早上,我們開始靈活運用JMeter做壓力測試。為了防止由於來自於客戶端成千上萬的併發執行緒訪問使伺服器不堪重負,並保證監控到的(併發)請求次數在可控範圍之內。我們讓執行緒緩慢增加,保持著伺服器的穩定性,統計每一秒總吞吐數,同時核查CPU的平均負載。接著,我們繼續增加執行緒,核查(的步驟)。通過截圖,我們記錄了相關資料並讓每個人都能看到。我們的目標是不僅要承受住高峰期的RPM,同時不額外增加響應時間。
隨著時間的推移,我們通過改變程式碼來快取資料庫的一些需求。同時,在週四之前,我們不斷更新最新需求來滿足我們的需要。這就是我們的部署過程。
我們所具備的環境:本地機器,開發,示例,分期,生產(local machines, dev, demo, staging, production)。通常情況下,在進入開發之前,會有一個指令碼測試執行(大約40分鐘)。
指令碼部署大概需要15—30分鐘(80%的人把時間花在git clone上)我們需要重複這一過程,與生產逐漸靠近。
而生產最主要的一個環境正在上演。如果我們將程式碼優化,我們需要測試效能,這將花費幾個小時部署過程才能有所變化。
相反,我們可以臨時進入伺服器,手動套用變更(利用ssh進入伺服器,vi編輯檔案,重新啟動mongrel),但是我們要在4臺伺服器上重複這一過程,取消auto-scale伺服器,重建auto-scale AMI,然後在連線auto-scale伺服器。即使你採取捷徑過程,仍需要20—30分鐘。
我們重複了好幾次,很小心的儲存資料,這時我們發現,我們正進入一個錯誤的方向。因為每秒(900rpm)能維持15個請求,而數量也隨之一天天的變少。聰明的資料庫管理員看到這裡也許會偷笑,知道是什麼原因造成的。但是我們沒有專門的DBA團隊,我的資料庫方面技能也有點生疏。
於是我們安裝了New Relic,自從幾年前我利用New Relic來開發Facebook應用程式後,便愛上了它。這是一個針對Rails和Web應用環境的效能監控解決方案。我只把它當做一款開發工具,因為它可以讓你看到應用程式所花費的時間。它同樣適用於生產監控,比如我們需要為職業規化付款,它還可以確定頁面請求執行慢的原因並且便於資料庫查詢並且對已執行的資料可提供很多細節。
因此,我們迫切希望得到更多的資料以便升級至New Relic Pro計劃中。我們有一些豐富的資料(資料庫請求緩慢原因和應用所要花費的時間)。
一個資料庫查詢時間花費了整個資料庫的95%。當我看到這一點時,我知道我們的表現變得很糟糕,這時我意識到在查詢上還有個未索引的領域。也許是因為JMeter測試在資料庫列表中建立了新條目,或許是因為已經執行了幾個小時的緣故,在資料庫中有成千上萬行,因此我們需要對整個條目進行掃描。
於是我檢查了資料庫中schema.rb檔案,發現有人錯誤的建立了一個multi-column索引(我想說避免建立multi-column索引,除非你知道需要一個特別的索引)。在多列索引中,如果你在查詢中指定A,B,C,你可以用它來查詢A,或者A和B,或者A和B和C。前提是必須依賴列,比如你無法脫離A列。
這在我們的案例中是真實存在的,所以,我立即為索引建立一個遷移。當然我們是在伺服器上遷移,這樣可以使伺服器有5分鐘時間去建立牽引,然後重新執行測試。每秒擴充套件到120個請求數(7.200 rpm),此時資料庫載入時間只有16%。
週三下午大約3點鐘,這時資料庫伺服器可以承受的負荷為每分鐘約36,000頁的請求數。我們再次突破伺服器CPU載入的時間。
就在這一天,我們已經刷爆了EC2例項,我們還有一支團隊一直在忙於擴充套件性測試,突破了極限。我們去除了AMIs一些不必要的執行,並要求其他人停止測試,我們反覆的請求並新增AWS(Amazon Web Services)限制,然後進入客戶代表的賬號,並讓他們提出升級需求。
週三晚上很晚我們才離開,此時我們信心倍加,相信能處理好週四早上的超負荷。
週四,我們所有人都在使用Google分析New Relic,它涵蓋了所有螢幕的後端介面,因為它將在PST(太平洋標準時間Pacific Standard Time)上午5:30開始舉行,我們遍佈在酒店的每個角落,用篝火進行實時聊天。
第一次載入的時間在5:38分,此時此刻發生了極大的變化:在短短的一分鐘時間裡從原有0訪量變成上千人訪問。頁面響應時間穩定在500ms,在伺服器客戶端頁面,沒出現過任何延誤。
當然,我們也出現Bug,這就要求在載入的時候必須要對此進行修補,然後重新啟動,(可以同時進行),還要確保頁面通暢。載入高峰值我們已測試過,當然,執行的非常好,這也遠遠超過了我們週一下午前所做的改變。因此,這兩天的辛苦,沒白費,我們獲得了回報。
經驗與教訓:
1.如果我們早些使用使用New Relic Pro計劃,那麼我們將節省很多時間,但是無論用哪種方法,我相信是New Relic拯救了我們的漏洞,如果我們在週三下午還沒有得到所需要的資料,那麼週四早上的一切都是扯談。
2.載入測試需要定期進行,而不是在大事件發生時才想起測試。在任何時候也許某人使資料庫發生改變,比如忘記了索引或是注入了一些其他的異常效能而又未被發現,直到系統載入時才知道。
3.需要一個相對安全的、自動化的方式便於提供執行加快部署分段和生產次數。必需制定捷徑的常規過程。而不是依賴於某人用vi編輯檔案,不會造成任何錯誤。
4.如果不使用雲服務,我們根本不會如此之快擴充套件應用程式例項和資料庫伺服器。
5.團隊的作用很重要,通常情況下,如果不是有危機發生我們是分散的,各司其職,也不會想到做些遙不可及的事情。