當地時間12月14日凌晨3點47分,谷歌遭遇大面積技術故障,多項服務受影響,直到約45分鐘後才恢復。谷歌在宣告中稱本次事故的原因是“內部儲存配額問題”。
可以想象,谷歌有多少工程師度過了一個不眠之夜?在分散式系統上線後,開發人員和運維人員到底能不能高枕無憂?當出現問題時,系統能否依然穩定執行?
第一個感恩節
客戶在夏天新推出了一個電商系統。推出產品之後那幾周和幾個月的經歷,一次又一次地證明:新推出一個網站就像養一個寶寶,某些事情必然會發生,例如半夜被喚醒,然後被告知一些“可怕”的發現,就好比聽到“天吶!你給孩子餵了什麼……橙色橡皮泥嗎?”然而,在處理完這個新系統遇到的所有問題之後,我們仍可以持謹慎樂觀的態度去迎接這個假日季。
我們的樂觀態度源於幾個因素。第一,生產伺服器的數量幾乎翻了一番。第二,我們有可靠的資料顯示該網站在目前的負載下能穩定執行。第三,我們有信心應對網站出現的任何錯誤。
為了過感恩節,有些同事在週末加班,從而在感恩節休假。我有4天假期,於是帶著妻兒去父母那團聚,吃感恩節大餐,他們的住所跟我們隔了3個州。我們還在感恩節的那個週末,安排了24小時的工作現場值班。正如我所說的,我們抱著謹慎樂觀的態度行事。當然,作為一名前童子軍成員(口號是“時刻準備著”),去父母家之前,我將膝上型電腦塞進了家用小貨車,以防萬一。
把脈當星期三晚上抵達父母家時,我立即在父母的家庭辦公室裡安置好了膝上型電腦,我可以在任何有寬頻和手機的地方工作。憑藉父母家的3兆有線寬頻,我使用PuTTY登入跳板機,並啟動我的取樣指令碼。
在新推出這個網站之前的準備階段,我參與了負載測試。測試完成後,大多數負載測試提供了測試結果。由於資料來自負載生成器而不是被測系統內部,因此這是一個“黑盒”測試。為了從負載測試中獲得更多資訊,我開始使用應用程式伺服器的圖形使用者介面,檢查一些重要的指標,如延遲、空閒堆記憶體數量、活動的請求處理執行緒數量和活動的會話數量。
如果事先不知道要找什麼,使用圖形使用者介面就是探索系統的好方法。如果明確知道想要什麼,使用圖形使用者介面就會變得乏味。但如果需要一次檢視三四十臺伺服器,那麼使用圖形使用者介面就完全不切實際了。
為了能從負載測試中獲得更多資訊,我寫了一個Perl模組的集合,實現圖形使用者介面螢幕抓取,並能解析HTML裡面的值。
這些Perl模組可以讓我獲取和設定屬性值,並呼叫應用程式伺服器的內建元件和自定義元件的各個方法。由於整個圖形使用者介面均基於HTML,因此應用程式伺服器不能區別Perl模組和Web瀏覽器。透過使用這些Perl模組,我可以建立一組指令碼,來取樣所有應用程式伺服器的重要統計資料,打印出詳細資訊和彙總結果,休眠一段時間,然後迴圈往復。
這些都是簡單的指標,但自從新網站推出以來,我們所有人都透過檢視這些統計資料瞭解網站的正常“節奏”和“脈搏”,比如我們一眼就知道,7月的一個星期二中午系統是正常的。
感恩節感恩節早上一醒來,我來不及喝完咖啡就跑進父母的辦公室檢視整晚執行的資料視窗,對所看到的資料檢查再三,清晨的會話數量已經達到了正常一週中最繁忙的高峰水平。訂單數量如此之高,我不得不打電話給DBA,瞭解是否有重複提交的訂單。答覆自然是沒有。
截至中午,顧客在半天內下的訂單量,已經與過去平常一週的訂單量相同。從頁面延遲、系統的響應時間和整體網站效能這些總體指標來看,系統雖然承受著壓力,但仍處於運維標稱範圍之內。
更好的是,即使會話和訂單的數量在不斷增加,但隨著時間的推移,這些資料也在趨於穩定,這讓我在整個感恩節火雞晚餐中興高采烈。
到了晚上,這一天內的訂單量已經達到了在此之前11月的總訂單量。到午夜時分,日訂單量已經與整個10月份的訂單量持平。即使是這樣,網站還在正常執行,它通過了第一次嚴酷的負載測試。
黑色星期五第二天是黑色星期五。用完早餐後,我走到家庭辦公室,看了一下資料。訂單數的增長趨勢甚至比前一天還要高,會話數量也增加了,但一切正常,頁面延遲仍然低至大約250毫秒。我決定帶著老媽出門買些做雞肉咖哩的食材。
當然,如果後來沒有出現可怕的錯誤,我是不會絮絮叨叨地講這個故事的。在我離開辦公桌之前,什麼狀況還都沒發生。果然,當在外邊走了一半路程時,我接到了電話。
“早上好,邁克爾。我是丹尼爾。”
“一定有麻煩了是不是,丹尼爾?”我問道。
“所有的DRP在SiteScope上都變紅了。我們一直在進行DRP的滾動重啟,但它們重啟後會立即失效。戴維已經召集了電話會議,並請你加入。”
雖然是寥寥數語,但我已從丹尼爾那裡得知,網站停機了,問題很嚴重。SiteScope模擬的其實就是真實的顧客,如下圖所示。
SiteScope變紅,表明顧客無法購物,我們正在損失收入。在一個使用ATG軟體的電商網站中,頁面請求由專用的例項處理。Web伺服器透過DRP協議呼叫應用程式伺服器,因此通常將應用程式伺服器上處理請求的例項稱為DRP。一個DRP變紅,表示應用程式伺服器上處理請求的一個例項已經停止響應頁面請求。所有DRP都變紅,則意味著該網站已經停機,並且正以大約每小時100萬美元的速度流失訂單。
生命體徵丹尼爾給我打電話時,事故大約已經過去20分鐘了。運維中心已將問題上報給現場團隊,團隊運營經理戴維請我參與解決。與顧客可能遭受的巨大損失相比,中斷休假根本不算什麼。
事故發生20分鐘後,我們知道了以下一些情況。
- 會話數量非常高,比前一天還要高。
- 網路頻寬使用率很高,但沒有達到極限。
- 應用程式伺服器的頁面延遲很高(響應時間很長)。
- Web、應用程式和資料庫CPU使用率非常低。
- 搜尋伺服器這個以往常見的罪魁禍首這次倒是響應良好,其統計資料看起來沒問題。
- 幾乎所有處理請求的執行緒處於忙碌狀態,其中許多已處理超過5秒。
為了獲得更多資訊,我開始獲取那些有異常行為的應用程式伺服器的執行緒轉儲。其間,我請在會議室現場工作的那位明星級工程師阿肖克,幫忙檢查後端訂單管理系統。他在後端看到了與前端類似的模式:CPU使用率很低,大多數執行緒長時間處於忙碌狀態。
從我接到電話到現在已經有近一小時了,或者說,網站已經停機80分鐘了。這不僅意味著訂單流失,還意味著這次極為嚴重的事故讓我們幾乎就要違背SLA。我討厭違背SLA,因此感到很不安。所有同事和我一樣,都很不安。
進行診斷前端應用程式伺服器上的執行緒轉儲顯示,所有的DRP都表現出相似的模式。有幾個執行緒忙著呼叫後端,而其他大部分執行緒則在等著呼叫後端的可用連線,等待中的執行緒全部被阻塞在一個沒有設定超時的資源池上。
如果後端停止響應,那麼進行呼叫的執行緒將永遠不會返回,而那些被阻塞的執行緒將永遠無法獲得呼叫後端的機會。簡而言之,所有3000個處理請求的執行緒,都被束縛在那裡動彈不得,這完美地解釋了所觀察到的低CPU使用率現象:100個DRP全部處於空閒狀態,一直在等待永遠不會獲得的響應。
再看一下訂單管理系統。該系統上的執行緒轉儲顯示,在其450個執行緒中,一些正忙著呼叫外部整合點。剩下的你也許已經猜到了:其他所有執行緒都在等待呼叫那個外部整合點。
求助專家當訂單管理系統的技術支援工程師撥入電話會議時,我感覺像是等了半個世紀(但可能只等了半小時)。他解釋說,通常處理送貨排程的4臺伺服器中,有2臺在感恩節這個週末因維護而停機,而另一臺由於未知原因失靈了。直到今天,我還是不知道為什麼他們會在全年52個週末中,偏偏選擇這個週末進行維護!
這種情況使得流量在幾個系統之間產生了巨大的失衡,如下圖所示。
當那位接到技術支援請求的工程師檢查那臺“孤獨”的送貨排程伺服器時,發現其CPU利用率已經達到100%。儘管已經多次收到CPU利用率過高的警告,但這位工程師還是沒有做出反應。該團隊經常因為CPU利用率的瞬間峰值收到警告提示,但結果常常是誤報。之前所有的誤報,讓他們學會忽略所有CPU高利用率警告。
在電話會議上,業務負責人語氣低沉地告訴我們,市場營銷部門在感恩節前準備了新的廣告插頁,登在感恩節第二天(星期五)的報紙上。廣告上提到,所有在感恩節後第一個星期一之前線上下的訂單,均享受免費送貨上門服務。在這個持續了4小時的電話會議上,所有參會者第一次陷入了沉默。
回顧一下,前端系統是一個電商系統,擁有分佈在100臺伺服器上的3000個執行緒,以及發生了根本性變化的流量模式(因為廣告促銷)。電商系統發出的請求流量,淹沒了其下游的訂單管理系統。訂單管理系統擁有450個執行緒,它們既可能被用來處理來自前端系統的請求,也可能被用來處理訂單。而訂單管理系統發出的請求又淹沒了其下游的送貨排程系統,後者一次只能處理25個請求。
這種狀況會隨著促銷宣傳而一直持續到星期一。這簡直就是噩夢,網站已停機,而且這種情況沒有手冊可以參考。我們正處於事故的“漩渦”中,不得不硬著頭皮解決問題。
如何應對接下來就做頭腦風暴。大家提出了許多方案,也否決了許多方案,否決的原因大多是在目前的情況下,應用程式程式碼的行為是未知的。此時唯一可行的方案逐漸變得清晰起來:停止發出如此多的請求來對訂單進行送貨排程預約。由於週末的市場營銷活動主要圍繞免費送貨上門,因此使用者下訂單的請求是不會放慢速度的。此時必須找到一種方法來抑制對送貨排程系統的呼叫數量,而訂單管理系統無法做到這一點。
當檢視電商系統的程式碼後,我們看到了一絲希望。電商系統的程式碼使用了標準資源池的一個子類,管理訪問訂單管理系統的連線。實際上,它還有一個單獨的連線池,專用於處理有關送貨排程的請求。電商系統擁有一個專用於處理送貨排程連線的元件,所以我們就可以將該元件用作限流器。
要是電商系統的開發人員為連線池添加了一個enabled屬性,那麼將其設定為false就會使事情變得很簡單。也許他們下一步就可以這樣做。不管怎樣,把資源池中最大的連線數設定為0也能有效地將資源池關閉。
尾聲
我編寫了一個新的指令碼,可以完成重置該連線池最大值所需的所有操作。它能設定max屬性,停止服務,然後重啟服務。
透過執行一個命令,運維中心或客戶現場“指揮所”(會議室)的工程師,就可以將最大連線數重置為任何所需的數值。我後來才知道,這個指令碼在整個週末都被不斷地使用著。
電話會議結束了。我結束通話電話,去哄孩子上床睡覺。這著實花了我一點時間,因為他們不停地說著各種趣聞:逛公園,在草坪上的噴灑器下邊玩兒,去後院看剛出生的兔寶寶。我很喜歡聽他們聊這些。