故障常見原因歸類分析及預防和應對措施


每一次故障都是一次寶貴的學習機會。

引語##

故障是開發者頭上懸着的一把劍。俗語曰:no zuo no die. 可是開發者很難做到 no zuo. 如何在 zuo 的時候防止 die 呢 ?

知己知彼才能百戰不殆。要避免故障,就需要對故障有一個相對深入的理解。

故障,一般是指一段時間內較為密集的問題發生導致了一定的負面影響。業務量小的極少影響面的問題不算故障,否則就會混淆真正的故障,導致受限資源投入分配不合理,影響關鍵問題的解決進度;零星的非密集的問題可能不是故障,因為那可能是小概率事件觸發了潛在BUG,需要解決,但定為故障有點勉強。

要避免故障,首先需要深入了解故障發生的原因。以下內容來自於對多起故障的分析、歸類和總結。

分析方法##

拿到一個故障,如何分析它 ? 如何從中學到最大的收獲 ? 如何給它進行歸類呢 ?

首先,要確立分析目標。 我重點關注的是故障發生的主要原因及預防措施,而不是現象及處理過程和時長。因此,可以概覽故障現象描述、處理過程及時長、次要因素等,除非其中有重要價值的內容;

其次,軟件的根本任務是處理數據。 故障的本質就是數據處理出錯了。 或者是 數據處理成非預期的結果,或者是數據處理延遲,或者數據展示有問題,或者兼而有之 。 因此,數據是故障分析的一個重要關注視角。

再次,處理可以抽象為算法。 處理有問題,或者是選用了錯誤的算法,或者是算法里新增或修改的部分對某些場景不適配,破壞了原有約定 ,或者兼而有之。因此,算法是故障分析的另一個重要關注視角。

最后,如果一個故障的原因有一個明確的判定,就可以為之定名;如果它不屬於已有的任何一個定名,就要新建一個定名,將其放入其中。


故障原因##

多發源###

故障多發源,是指發生故障的最常見原因。謹防這幾種情形,可以預防大部分的故障可能性。

核心流程出錯####

核心流程的某個環節出問題,導致整體流程失敗,或者部分業務場景的整體流程失敗,都會導致密集問題發生。通常是在主流程中添加了一段代碼,而這段代碼沒有考慮到某個場景或者健壯性不佳,影響了整體;在測試的時候,只驗證了改動點部分,沒有回歸核心流程,或者回歸了核心流程,卻遺漏了某個場景的回歸。

預防措施:

  1. 評估改動點! 非常重要!哪怕只有一行,只要在主流程中,都要仔細評估其影響范圍。在主流程中添加的代碼越長,越要警惕。
  2. 增加必要的 try-catch 。如果增加的代碼只有局部影響,可以添加必要的 try-catch,防止未預料的情形的處理異常影響整體流程。
  3. 最好不要輕易改動影響全局的通用方法和配置(影響面和回歸面非常大); 盡可能只新增而不是修改。
  4. 覆蓋全面的核心流程的測試用例,每次發布都需要回歸通過。
  5. 有風險性的改動,增加開關。一旦出錯,立即關閉改動。

真實案例:

場景遺漏####

業務會逐漸發展成龐然大物,隨着人員的流動,很多業務知識和場景會逐漸被淡忘。新進的同學如果沒有充分評估到各種場景,就很容易以為遺漏某個場景,導致問題。要解決這種原因,是比較棘手的。

預防措施:

  1. 沉淀業務文檔和業務場景。
  2. 有全景圖意識,不限於眼前的一畝三分地。
  3. 有熟悉業務的同學進行方案評估和 CodeReview 。

缺乏健壯性####

實現服務之后,健壯性是保證服務能夠平穩運行、正確應對錯誤和異常的第一道關卡,也是合格程序員的必備代碼素養之一。

健壯性不佳,很容易導致由於未預料的局部細節、臟數據、局部調用失敗影響整體的流程和展示。

預防措施:

  1. 思考錯誤和異常,多多益善。
  2. 善用 try-catch 保駕護航。
  3. 使用空字符串、空列表替代 null 。
  4. 異常分支的測試覆蓋。

真實案例:

  • 由於一個 null 值導致整個訂單列表加載失敗。
  • 由於一個次要的依賴出錯導致整個詳情頁加載失敗。
  • 異常分支的代碼有問題,但沒有測試;當流程走到異常分支代碼,任務直接跪掉,反復重啟和跪掉。

瞬時大流量####

瞬時大流量是造成故障的一大殺手。 瞬時大流量,會導致機器資源短缺,CPU 飆升或內存爆滿或網卡、連接數打滿,直接影響整體服務的穩定性。

對於消息處理應用來說,瞬時大流量會導致消息處理延遲,業務狀態流轉滯后,影響后續環節;對於非消息處理應用來說,則會導致任務處理阻塞,接口響應變慢或不響應。

低性能、低吞吐量在面臨持續多個較大業務量的沖擊時,很容易出現阻塞、延遲;如果應用無限流,或限流失效,或限流不夠精確,都可能難以抵擋大流量的侵襲。

預防措施:

  1. 集群環境:保證集群各機器或 Region 的負載均衡;
  2. 單機環境:有針對性地限流、限速和限數,並嚴格測試和驗證。
  3. 壓測演練。容器化后的壓測。
  4. 減少或消除過重的鎖邏輯。
  5. 大流量預警和感知。比如線程池隊列阻塞預警,計算出的大數據集處理的預警 等。
  6. 批量調用替換循環單個調用;O(nlogn) 算法;減少不必要的訪問和服務依賴。

真實案例:

極端情況####

極端情況是指,一些很罕見的事件的發生挑戰了系統的某個局部極限,導致系統出了問題。

比如說,一個訂單內的商品種數通常不會超過 10 ,但商家或買家刷單,導致大量含有 50 多個商品的訂單,然后密集導出,就會導致應用 FullGC 嚴重,引起接口響應超時或任務無法進行下去。

預防措施:

  1. 思考極端情形及影響;
  2. 提前做好極端情形測試和設計方案。

真實案例:

依賴失敗####

依賴失敗有如下情形:

  1. 所依賴的服務、配置或變量不存在或處於不合適的版本,導致應用啟動失敗,或者啟動后的服務不能正常運行;
  2. 所依賴的基礎服務不穩定出現大量報錯時,會導致依賴它的高頻應用也出現大量報錯,導致雪崩效應。
  3. 配置或服務循環依賴,導致死循環。

預防措施:

  1. 當一個項目發布涉及多個系統或許多細節時,就需要編寫發布文檔,仔細指定發布配置和順序,保證應用依賴的正確性。在具體發布時,則要嚴格執行發布文檔里指定的檢查點清單和發布順序。檢查依賴項:API 版本、Jar 版本、依賴服務、配置項、DB 字段。
  2. 自動降級。嚴格控制超時,隔離或去掉不必要的弱依賴。
  3. 制定明確的依賴原則,上游依賴基礎,避免循環依賴。
  4. 前后端對接口約定的返回值及格式溝通達成一致。

資損####

資產是客戶非常敏感的私有產權。發生資損時,通常是最高故障級別。

資損一般發生在:1. 直接資損: 系統處理未考慮冪等,導致重復消息多次處理;2. 業務方根據基礎服務方的某些字段進行資金業務處理,而字段返回值有誤,導致少算或多算。3. 誘導性資損,由於某些展示信息,誘導用戶做出某種難以追回的行為,比如已發貨訂單展示為待發貨;

預防措施:

  1. 直接處理資金業務,注意冪等處理;
  2. 有依賴狀態的資金業務處理?
  3. 消除誘導性信息;
  4. 對資金計算敏感,尤其注意邊界值處理,避免 +1 或 -1 導致問題。

新舊遷移出錯####

多發生於技術重構優化的時候。比如舊的領域模型遷移新模型、舊的技術棧遷移新技術棧、舊的頁面遷移新頁面。做技術改造,側重點往往在於新服務的測試,而容易忽略老服務的測試兼容。

新舊遷移存在一個權衡:徹底還是減少出錯。更為徹底的遷移,出錯和故障概率會更大,但新系統會更加清爽;向老系統作一些妥協,可以減少一些出錯和故障概率,但新系統會帶着老系統的包袱前行,后續依然會出問題。

預防措施:

  1. 分流。 分流可以確保新服務上線之后的影響面逐漸擴大,即使有未考慮的點,也會將影響面控制在最小范圍。
  2. 充分測試,事先評估好測試用例並嚴格執行。
  3. 舊接口遷移到新接口時,返回值的結構和值約定最好一致,確保 新對新,老對老,避免“新對老”的不兼容導致問題。
  4. 老的頁面和功能要回歸全面,避免重要場景遺漏。
  5. 新的和老的代碼改動分開 CR ,分批 CR 。

老代碼####

不可否認,老代碼在企業初創期曾立下汗馬功勞。可是,隨着時間推移,業務量越來越大,復雜度也在快速增加,很多老代碼的簡單處理就逐漸變成了“定時炸彈”,冷不防讓地震一震,讓人抖一抖。

預防措施: 定期梳理和清除。

真實案例:

數據泄露####

數據安全性越來越成為企業的重要關注點。對於 SaaS 來說,要保證各個租戶的數據和操作互不影響,不能看到和操作未授權的數據。

預防措施: 1. 敏感數據脫敏; 2. 避免覆蓋; 3. 權限控制; 4. XSS 安全問題。

真實案例:

設備及網絡####

設備及網絡屬於互聯網的基礎設施,位於最底層,一旦出現問題,影響面也是巨大的。當設備老舊出現硬件故障或宕機,或者網絡抖動或突然斷開,也是很容易導致大面積失敗。

預防措施:

  1. 定期檢查和更換老舊設備。花點錢更換老舊設備,比宕機出現問題花費時間、精力和金錢補償,要划算得多。
  2. 備用鏈路和機房。
  3. 避免單點故障。

操作不當####

操作不當主要有如下情形:

  1. 兩種操作同時進行,發生沖突導致出錯;
  2. 代碼合並沖突解決不當;
  3. 操作不規范,引發系統處理失常或失控。

預防措施:

  1. 代碼合並沖突解決,雙方確認。
  2. 同時更改系統配置,需要溝通協調順序,避免並發。
  3. 批量數據修復方案要仔細 CodeReview , 當做正式發布處理;
  4. 批量數據修復在業務低峰期進行,除非是緊急修復。
  5. 變更操作的工具自動化,審批流程。

真實案例:

  • 在業務量比較大的情形下,對表進行 truncate 操作可能會導致數據庫 hang 住。

其他原因###

資源未隔離####

底層集群未能隔離不同業務的資源, A 業務的大流量導致集群機器資源被打滿,間接影響了 B 業務。

預防措施:

  • 規划和實現資源隔離策略:重要業務和次要業務的資源進行隔離;不同業務的資源進行隔離。

臟數據####

由於臟數據缺乏整體的關聯性約束,應用讀取到臟數據,容易出錯;如果應用有一連串的邏輯處理,可能生成更多的臟數據,引出更大的麻煩。

預防措施:

  1. 檢測和消除臟數據。
  2. 避免在線上造測試數據。

故障處理##

發生故障時,第一反應不是立即排查原因,而是立即止損,將影響面最小化。

  • 若能確定是發布導致,立即回滾發布。 回滾發布后,再仔細排查原因。
  • 及時同步進度,讓關注方知悉;
  • 建立快速同步機制,預防小問題演變成大故障。

為了更好地減少故障的可能性,還需要事先做好故障應急預案。

  • 梳理底層的強弱依賴,確定強依賴不可用時導致的影響面;
  • 當強依賴不可用時,能夠快速恢復的方案,將影響面降低到最小。
  • 故障演練。模擬大流量、極端情況和故障情形發生,檢測應急預案是否生效和快速恢復。

根因探討##

故障層出不窮,現象眼花繚亂,究竟從何處來,去往何處呢 ? 是否有根本規律可循 ?

事實上,絕大多數的軟件故障都是具備內在的邏輯關聯的。從基礎邏輯關聯來推理,可以推斷出很多本可以預防和避免的問題。與正常流程相比,故障本身也是一種路徑,產生出特定的數據集,只是這些數據集及引發的現象是不符合人們的預期的。以下是部分基礎邏輯關聯分析:

  1. 依賴問題。 一個功能會依賴某個字段、配置、校驗、接口約定等;當字段或配置變更不合理,或語義發生變化時,會導致問題; 兩個業務 A 和 B 均依賴同一段代碼 c ,根據對 A 的某個需求在代碼 c 修改了一些邏輯 ,影響了 B ,結果導致 B 出了問題;在原有流程中多了不必要的校驗或少了必要的校驗,會導致問題;原來依賴格式 A,遷移新服務后變成了不兼容的格式 B。 絕大部分功能性問題都可以歸結為評估影響面不准確。
  2. 流量問題。通常是大流量或極端情形導致,超過了系統能夠承載的閾值。對系統的閾值壓測摸底並提前預估好容量,輔以經過嚴格測試和驗證過的限流、限速等。
  3. 環境問題。依賴的環境假設出現問題,導致依賴鏈路阻斷,從而引發各種問題。需要在運維層面保證環境的高可用高可靠性,避免單點。

小結##

故障,是每個開發者乃至企業法人都不願意經歷的事情。可是,每一次故障,都蘊含着不同形式的疏忽、未知、真理,正向思考,其實是一次非常珍貴的學習機會。故障,也會引導人抵達更深入的境地,去理解事情的本質與關聯。正視故障,從故障里學習真知,預防和避免故障,乃是更佳的姿勢。

要預防故障:

  • 第一是細心。多個心眼,准確評估影響面,兼顧考慮老業務老功能的回歸, 仔細檢查依賴項,保證返回約定的一致性,規范執行;
  • 設計和實現要考慮健壯性、大流量和極端情形,避免低性能。
  • 有針對性避免安全性和資損問題。
  • 設置嚴密的監控報警,在問題的萌芽期掐滅。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM