天天看點

自定義異常為什麼性能差,我來告訴你

本文源自 公-衆-号 IT老哥 的分享

IT老哥,一個在大廠做進階Java開發的程式員,每天分享技術幹貨文章

老哥哔哔叨

大家應該都經曆過

雙十一

吧,那個流量大的

恐怖

吧,那個并發高的

吓人

吧。那麼在一個高并發的系統裡,有哪些點是影響系統性能的呢,今天我們來講其中一個點:

自定義異常

如果對大家有所幫助,請給個【在看】和【點贊】

瘋狂的異常

為什麼異常會影響性能

首先給大家看一段

JDK

Throwable

源碼

public synchronized Throwable fillInStackTrace() {
    if (stackTrace != null ||
        backtrace != null /* Out of protocol state */ ) {
        fillInStackTrace(0);
        stackTrace = UNASSIGNED_STACK;
    }
    return this;
  }           

複制

上面這段

JDK

的源碼就是抛出異常時會調用的方法,這段方法暴露出兩個問題

  • 使用了

    synchronized

    修飾整個異常方法
  • 将異常追蹤資訊放到了堆棧中(想想

    JVM

    線程

異常種類

  • 業務異常這些是我們自定義的、可以預知的異常,抛出這種異常并不表示系統出了問題,而是正常業務邏輯上的需要,例如使用者名密碼錯誤、參數錯誤等。
  • 系統異常往往是運作時異常,比如資料庫連接配接失敗、IO 失敗、空指針等,這種異常的産生多數表示系統存在問題,需要人工排查定位。

相信大家都接觸過異常,對于業務異常,我們隻需要簡單的知道一個描述問題的字元串即可,棧追蹤資訊對我們的意義并不大。而對于系統異常,追蹤資訊才是排查錯誤不可或缺的參考。

大家試想,如果前端傳的參數錯了,系統裡就抛出一個異常,那麼在雙十一的情況下一秒鐘得抛出多少個異常呢?

問題思考

  • 抛異常的時候是不是會被 synchronized 上同步鎖?
  • 需不需要線程去執行?
  • 是不是得建立異常對象?
  • 需不需要堆棧去存儲?
  • 需不需要 jvm 去垃圾回收?

性能測試

  • 建立普通 Java 對象 (CustomObject extends HashMap)
  • 建立普通 Java 異常對象(CustomException extends Exception)
  • 建立改進的 Java 業務異常對象 (CustomException extends Exception,覆寫 fillInStackTrace 方法,并且去掉同步)

測試結果

(運作環境:xen 虛拟機,5.5G 記憶體,8 核;jdk1.6.0_18)

(10 個線程,建立 10000000 個對象所需時間)

  • 普通 Java 對象:

    45284 MS

  • 普通 java 異常:

    205482 MS

  • 改進的 Java 業務異常:

    16731 MS

大家可以看到正常抛出 Exception 的和覆寫了 fillInStackTrace 的 Exception,性能差距了很多倍,如果高并發的系統裡,就像雪球一樣越滾越大。影響系統的并發量。

解決方案:覆寫 fillInStackTrace

我們來看看非常 NB 的

kafka源碼

是如何優化的。

自定義異常為什麼性能差,我來告訴你
/* avoid the expensive and useless stack trace for api exceptions */
/* 翻譯:避免對api異常進行昂貴且無用的堆棧跟蹤 */
@Override
public Throwable fillInStackTrace() {
    return this;
}           

複制

很多開源架構都是這樣處理,避免不必要的性能浪費。

老哥結語

什麼是匠人精神,就是将一件事情做到極緻。優化永無止境,且行且珍惜。

雲伺服器,雲硬碟,資料庫(包括MySQL、Redis、MongoDB、SQL Server),CDN流量包,短信流量包,cos資源包,消息隊列ckafka,點播資源包,實時音視訊套餐,網站管家(WAF),大禹BGP高防(包含高防包及高防IP),雲解析,SSL證書,手遊安全MTP,移動應用安全、 雲直播等等。