互聯網系統可靠性基礎:正確的異常處理
系統的可靠性比性能、高并發更重要。沒人希望整天分析錯誤數據、修復錯誤數據。任何一個錯誤都可能導致客戶的損失。
成熟可靠的系統和不可靠系統之間的差別很大程度取決于:異常的正確處理。經驗豐富的程序員和經驗不豐富的程序員之間的差別是是否考慮到并正確處理可能發生的各種異常。這和處理用戶輸入數據的驗證非常相似。
可以發現即使是運行多年,知名的互聯網公司的大型系統都或多或少存在異常處理的問題。
- Try catch 的內部實現
- 異常必然發生
- 異常處理的性能考慮
- 幾個理念:Fail fast vs Retry vs Let it crash
- 異常和錯誤的類型
- 守門員:全局異常處理
- 異常處理總結和一些原則<
- 一些有用的資料
Try catch 的內部實現
這篇文章有比較簡單的解釋。
異常必然發生
原因很多種:比如,網絡不穩定、硬件故障、軟件 BUG 等等。很多程序員如果不接觸系統運維的話,很難考慮到并處理這些異常。這是問題原因的一部分。從這個角度看全棧工程師還是非常有用的。
異常處理的性能考慮
Donald Knuth: “premature optimization is the root of all evil”.
考慮到這個問題其實就已經進入的誤區。該交給編譯器的事情還是不要過多考慮了。
應該使用 try catch 的情況
說道 try catch 的性能,可能很多人會想到這會讓程序變慢。其實不然,雖然它對應用性能的影響取決于編譯器的實現,但是大部分情況下不會影響不拋異常情況下的執行速度,但 是會有稍微內存占用的提升。并且,假如異常很少觸發,使用 try catch 取代錯誤碼然后 if 判斷的方式可以提升速度,因為你不需要沒次都判斷這個錯誤碼。使用異常代替判斷還可以讓程序更可讀。
不應該使用 try catch 的情況
異常處理不應該取代 if else 用來做流程控制。
幾個理念:Fail fast vs Retry vs Let it crash
Let it crash & Fail Fast
可以讓錯誤停止擴散,保證整個系統的健康。可以重置系統狀態、清理變量和內存占用等等。讓系統恢復到原始狀態。這對于恢復系統異常非常有用。
重試 Retry
在操作冪等的情況下,假如第一次操作失敗,重試是處理異常的很好的方式。但是注意自動重試在處理不當的情況下會引起操作數量持續增長導致系統雪崩。
異常和錯誤的類型
第一種劃分:
1. 編譯錯誤:很明顯,易處理。<br />
- 應用邏輯錯誤:最難發現問題;(類型系統有助于避免邏輯錯誤。Let it crash 主要針對邏輯錯誤和 BUG。)
- 運行時錯誤:終止進程。
- 生成的異常:throw 異常,盡早發現問題。</p>
第二種劃分:
- 內部異常:可以內部恢復的異常;一般記錄日志或打印相關信息告知用戶錯誤。
- 內部異常:可以內部恢復的異常;一般記錄日志或打印相關信息告知用戶錯誤。
- 外部異常:Error,只能從外部恢復;一般打印堆棧信息并退出。還包括 RuntimeException, 軟件 BUG,邏輯錯誤,錯誤使用
API。比如 NullPointerException。Let it crash 就是主要針對邏輯錯誤。這需要人工介入及時修復 BUG 。</p>
異常區分的簡單原則:
如果應用可以從異常中自動恢復,則為內部異常。如果不能,則為外部異常。
守門員:全局異常處理
這是處理不可預見異常的非常有用的一種方式。應用初成,難免考慮不到所有需要處理的異常。可以用全局異常處理記錄日志、發現問題。
Node.js:
process.on('uncaughtException', function(err) { console.log(err) });
Java:
package cn.eood.blog.example;
public class ExceptionHandlerExample { public static void main(String[] args) throws Exception {
Handler handler = new Handler();
Thread.setDefaultUncaughtExceptionHandler(handler);
Thread t = new Thread(new MyThread(), "My Thread");
t.start();
Thread.sleep(100);
throw new RuntimeException("Thrown from Main");
}
}
class Handler implements Thread.UncaughtExceptionHandler { public void uncaughtException(Thread t, Throwable e) { System.out.println("Throwable: " + e.getMessage()); System.out.println(t.toString()); } }
class MyThread implements Runnable { public void run() { throw new RuntimeException("Thrown From Thread"); } }</pre>
PHP:
function exception_handler($exception) { echo "Uncaught exception: " , $exception->getMessage(), "\n"; }set_exception_handler('exception_handler');
throw new Exception('Uncaught Exception'); echo "Not Executed\n"; ?></pre>
異常處理總結和一些原則
- 異常處理是互聯網架構中很重要的一部分。需要在架構中集成到日志系統、監控系統、通知系統。(可能很多公司的“架構”還沒有這幾個必須的系統,它們是可靠性的基礎。)
- 正確區分內部處理異常和外部處理異常。能內部處理的內部處理;否則 Fail fast 交由外部處理。
- 異常可以大致分為:系統層異常和應用層異常。正確區分系統層異常和應用層異常。這也和內部處理和外部處理對應。
- 不要用異常處理掩蓋錯誤,不用吞噬異常,至少需要記錄日志,以供查看分析。
- Web 系統異常情況下返回正確 HTTP 錯誤碼。這非常有利于發現和處理問題。正確使用 200 500 狀態碼。
一些有用的資料
- http://www.codeproject.com/Tips/490765/If-else-instead-of-try-catch
- http://www.microsoft.com/msj/0197/Exception/Exception.aspx
- http://www.codeproject.com/Articles/2126/How-a-C-compiler-implements-exception-handling
- http://stackoverflow.com/questions/490773/how-is-the-c-exception-handling-runtime-implemented
- http://stackoverflow.com/questions/307610/how-do-exceptions-work-behind-the-scenes-in-c
- http://stackoverflow.com/questions/16784601/does-try-catch-block-decrease-performance
- http://stackoverflow.com/questions/8255878/try-catch-performance-java
- http://stackoverflow.com/questions/16451777/is-it-expensive-to-use-try-catch-blocks-even-if-an-exception-is-never-thrown
- http://learnyousomeerlang.com/errors-and-exceptions
- http://www.nomachetejuggling.com/2006/06/13/java-5-global-exception-handling/
- http://php.net/manual/en/function.set-exception-handler.php
- https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
- https://docs.oracle.com/javase/tutorial/essential/exceptions/definition.html