🎯 什麼情境該想到我

當你「覺得這種小事不會出問題」的時候——高併發與海量資料下,很多平常根本不是問題的問題都會冒出來。

⚙️ 怎麼用(九個真實故障 × 教訓)

  1. 寫日誌也會引發故障 —— 現象:某應用伺服器叢集發布後不久,多台伺服器相繼報警,硬碟可用空間低於警戒值,很快有伺服器宕機;登入線上伺服器發現 log 資料夾檔案迅速增加,不斷吃掉磁碟空間。/原因:這是普通應用伺服器叢集,不需要儲存資料,因此配的是一塊 100GB 的小硬碟,裝完作業系統、Web 伺服器、Java 虛擬機、應用程式後只剩幾十 GB,正常情況夠用;但開發人員把 log 輸出的 level 全域配置為 Debug,一次簡單的 Web 請求就產生大量 log 輸出,高併發下很快耗盡磁碟。/教訓:應用程式自己的日誌輸出配置和第三方元件日誌輸出要分別配置;檢查 log 配置檔,日誌輸出級別至少為 Warn,並檢查 log 輸出的程式碼呼叫,呼叫級別要符合其真實日誌級別;有些開源第三方元件也會不恰當地輸出太多 Error 日誌,需要關掉這些第三方庫的日誌輸出——至於哪些第三方庫有問題,只有在遇到問題時才知道。

  2. 高併發存取資料庫引發的故障 —— 現象:某應用發布後,資料庫 Load 居高不下,遠超過正常水準,持續報警。/原因:檢查資料庫,發現報警是某條 SQL 引起的;這條 SQL 只是一條簡單的、有索引的資料查詢,本來不該引發報警。繼續查,發現這條 SQL 執行頻率非常高,遠遠超過正常水準;追查下去,發現它被網站首頁應用呼叫——首頁是被存取最頻繁的網頁,這條 SQL 被首頁呼叫,也就被頻繁執行了。/教訓:首頁最好是靜態的;首頁不應該存取資料庫,首頁需要的資料可以從快取伺服器或者搜尋引擎伺服器獲取。

  3. 高併發情況下鎖引發的故障 —— 現象:某應用伺服器不定時因為回應逾時而報警,但很快又逾時解除、恢復正常,如此反覆,讓維運人員非常苦惱。/原因:程式中某個單例物件(singleton object)裡多處使用了 synchronized(this),由於 this 物件只有一個,所有併發請求都要排隊獲得這唯一的一把鎖。一般情況下都是簡單操作,獲得鎖、迅速完成、釋放鎖,不會引起執行緒排隊;但某個需要遠端呼叫的操作也被加了 synchronized(this),這個操作只是偶爾被執行,但每次執行都需要較長時間才能完成,這段時間鎖被佔用,所有使用者執行緒都要等待,於是回應逾時;等這個操作執行完釋放鎖,其他執行緒迅速執行,逾時就解除了。/教訓:使用鎖操作要謹慎。

  4. 快取引發的故障 —— 現象:沒有新應用發布,但資料庫伺服器突然 Load 飆升,並很快失去回應;DBA 把資料庫存取切換到備機,Load 也很快飆升並失去回應,最終引發網站全部癱瘓。/原因:快取伺服器在網站伺服器叢集中地位一直比較低,伺服器配置和管理級別都比其他伺服器低一些。大家都認為快取是改善效能的手段,丟失一些快取也沒什麼問題,有時候關閉一兩台快取伺服器也確實對應用沒有明顯影響,所以長期疏於管理快取伺服器。結果這次一個缺乏經驗的工程師關閉了快取伺服器叢集中全部十幾台 Memcached 伺服器,導致網站全部癱瘓的重大事故。/教訓:當快取已經不僅僅是改善效能、而是成為網站架構不可或缺的一部分時,對快取的管理就需要提高到和其他伺服器一樣的級別。

  5. 應用啟動不同步引發的故障 —— 現象:某應用發布後,伺服器立即崩潰。/原因:應用程式 Web 環境使用 Apache + JBoss 的模式,使用者請求透過 Apache 轉發給 JBoss。發布時 Apache 和 JBoss 同時啟動,由於 JBoss 啟動時需要載入很多應用並初始化,花費時間較長,結果 JBoss 還沒完全啟動,Apache 就已經啟動完畢開始接收使用者請求,大量請求阻塞在 JBoss 行程中,最終導致 JBoss 崩潰。網站還有很多類似場景,都需要後台服務準備好、前台應用才能啟動,否則就會導致故障——這種情況被內部人戲稱作「姑娘們還沒穿好衣服,老鴇就開門迎客了」。/教訓:老鴇開門前要檢查一下姑娘們是否穿好了衣服。就本例來說,在應用程式中加入一個特定的動態頁面(比如只回傳 OK 兩個字母),啟動腳本先啟動 JBoss,然後在腳本中不斷用 curl 命令存取這個特定頁面,直到收到 OK,才啟動 Apache。

  6. 大檔案讀寫獨佔磁碟引發的故障 —— 現象:某應用主要功能是管理使用者圖片,接到部分使用者投訴,表示上傳圖片非常慢,原來只需要一兩秒,現在需要幾十秒,有時等半天結果瀏覽器顯示伺服器逾時。/原因:圖片需要使用儲存,最可能出錯的地方是儲存伺服器。檢查儲存伺服器,發現大部分檔案只有幾百 KB,而有幾個檔案非常大、有數百 MB,讀寫這些大檔案一次需要幾十秒,這段時間磁碟基本被這個檔案操作獨佔,導致其他使用者的檔案操作緩慢。/教訓:儲存的使用需要根據不同檔案類型和用途進行管理,圖片都是小檔案,應該使用專用的儲存伺服器,不能和大檔案共用儲存;批次處理用的大檔案可以使用其他類型的分散式檔案系統。

  7. 濫用生產環境引發的故障 —— 現象:監控發現某個時段內,某些應用突然變慢,內部網路存取延遲非常厲害。/原因:檢查發現該時段內網卡流量也下降,但沒找到原因;過了一陣子才知道,原來有工程師在線上生產環境進行效能壓力測試,佔用了大部分交換機頻寬。另一類同樣的濫用:網站資料庫有專門的 DBA 維護,如果發現資料庫存在錯誤記錄、需要進行資料訂正,必須走資料訂正流程、申請 DBA 協助;於是就有工程師為了避免麻煩,直接寫一段資料庫更新操作的程式碼,悄悄放到生產環境應用伺服器上執行,神不知鬼不覺地訂正了資料——但如果不小心寫錯了 SQL,後果可想而知。/教訓:存取線上生產環境要規範,不小心就會導致大事故。

  8. 不規範的流程引發的故障 —— 現象:某應用發布後,資料庫 Load 迅速飆升、超過報警值,回滾發布後報警解除。/原因:發現該應用發布後出現大量資料庫讀取操作,而這些資料本來應該從分散式快取讀取;檢查快取,發現資料已經被快取了;檢查程式碼,發現存取快取的那行程式碼被註解掉了。原來工程師開發的時候為了測試方便,特意註解掉讀取快取的程式碼,結果開發完成後忘記把註解去掉,直接提交到程式碼庫並被發布到線上環境。/教訓:程式碼提交前使用 diff 命令進行程式碼比較,確認沒有提交不該提交的程式碼;加強 code review,程式碼在正式提交前必須被至少一個其他工程師做過 code review,並且共同承擔因程式碼引起的故障責任。

  9. 不好的程式設計習慣引發的故障 —— 現象:某應用更新某功能後,有少量使用者投訴無法正常存取該功能,一點擊就顯示出錯訊息。/原因:分析這些使用者,都是第一次使用該功能;檢查程式碼,發現程式根據歷史使用記錄建構一個物件,如果該物件為 null,就會導致 NullPointException。/教訓:程式在處理一個輸入的物件時,如果不能明確該物件是否為空,必須做空指標判斷;程式在呼叫其他方法時,輸入的物件盡量保證不是 null,必要時建構空物件(使用空物件模式)。

🧪 我實際套用的紀錄

  • (待填)

⚠️ 注意 / 什麼時候不適用

  • 這是一份經驗清單,不是檢查表的全部。書中列的是「網站的典型故障」,價值在於提醒你:在高併發和海量資料的情況下,很多一般情況下不是問題的問題都會湧現出來。照抄九條並不能免疫,重點是養成「這件小事在高併發下會變成什麼樣」的直覺。
  • 別把它讀成「要加更多防護機制」。書中 13.10 小結引一位軟體技術前輩:「軟體設計有兩種風格,一種是將軟體設計得很複雜,以使其缺陷沒那麼明顯;一種是將軟體設計得很簡單,以使其沒有明顯的缺陷。」在網站的快速發展衝擊下,即使是不明顯的缺陷也會很快凸顯出來——所以答案通常是設計得更簡單,問題能很快被發現,解決也相對容易。
  • 這種知識只能靠經歷換。書中開篇說:大型網站的架構師最有價值的地方,不在於他們掌握了多少技術,而在於他們經歷過多少故障;培養一個網站架構師的成本,不單要看付了他多少薪水、給了他多少股票,還要看為他引起的故障買了多少次單。

🔗 相關工具