.net core 拋異常對效能影響的求證之路

Jlion發表於2022-06-11

一、前言

在.net 相關技術群、網路上及身邊技術討論中看到過關於大量拋異常會影響效能這樣的結論,心中一直就存在各種疑問。專案中使用自定義異常來處理業務很爽,但是又擔心大量拋業務異常存在效能問題。
查閱了各種文件,微軟官方對效能優化這一塊也不建議使用過多的異常,故我心中冒出疑問。

  • 疑問一:專案中大量丟擲業務異常對效能是否會受到影響?

二、求證

壓測環境

  • 伺服器:阿里雲伺服器
  • 宿主機:4C16G*1臺
  • pod 配置:3C3G*1pod
  • 壓測指令碼:堡壘機上,網路等相關影響條件比較小

2.1 使用.net 6 建立了一個簡單的web api 專案 新增兩個壓測介面

  • api介面程式碼如下
        /// <summary>
        /// 正常返回資料介面1
        /// </summary>
        /// <returns></returns        
        [HttpGet("Test1")]
        public async Task<IActionResult> Test()
        {
            return Content("1");
        }

        /// <summary>
        /// 拋異常返回介面2 ,同時存在全域性過濾器
        /// </summary>
        /// <returns></returns        
        [HttpGet("Test2")]
        public async Task<IActionResult> Test2(string open)
        {
            throw new BusinessException(Model.EnumApiCode.SignWrong);
        }
  • 全域性過濾器程式碼如下
    /// <summary>
    /// 全域性異常日誌
    /// </summary>
    public class ExceptionFilter : IExceptionFilter
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        public void OnException(ExceptionContext context)
        {
            //不做任何處理,直接返回1
            context.Result = new JsonResult("1");
        }
    }

   //全域性過濾器注入
   services.AddControllers()
       .AddMvcOptions(option =>
       {
             option.Filters.Add<ExceptionFilter>();
       });
  • 現在對test1 介面併發200的情況下進行壓測,持續15分鐘的壓測結果如下:

  • 對通過全域性過濾器捕獲異常並大量丟擲異常 在相同壓測條件情況下的壓測結果如下:

  • 對test1 和test2 同等條件下壓測結果對比
介面 tps cpu 壓測條件
test1 10300左右 cpu消耗90%左右 併發200,持續壓測
test2 4300左右 cpu消耗100%左右 併發200,持續壓測

目前得到的結論是拋異常確實影響效能,並且對效能下降了60% 左右,上面主要是異常流程走了全域性過濾器方式,故參考意義不大,下面再進一步修改程式碼進行壓測

  • 對test2 程式碼進行修改如下
       /// <summary>
        /// 拋異常返回介面2 ,直接try catch 不走全域性過濾器
        /// </summary>
        /// <returns></returns        
        [HttpGet("Test2")]
        public async Task<IActionResult> Test2()
        {
            try
            {
                throw new BusinessException(Model.EnumApiCode.SignWrong);
            }
            catch (Exception ex)
            {
                return Content("1");
            }
        }
  • 再對修改後的test2 介面進行壓測,壓測結果如下:

介面 tps cpu佔用 壓測條件
test1 10300左右 90% 左右 併發200,持續壓測
test2 9200左右 91% 左右 併發200,持續壓測

進一步得到的結論是try catch 後效能有所提高,跟正常相比還有點點差距,全域性過濾器對效能影響比較大,相當於走了管道,但是觀察程式碼test1 和test2程式碼還存在差距,懷疑test2 程式碼中new 了新異常導致效能差異,故再進一步進行程式碼修改求證

  • 對test1 程式碼進行修改,修改後的程式碼如下:
        /// <summary>
        /// 正常返回資料介面1,但是先new 異常出來,保持跟上面test2 程式碼一致
        /// </summary>
        /// <returns></returns        
        [HttpGet("Test2")]
        public async Task<IActionResult> Test2(string open)
        {
            var ex= new BusinessException(Model.EnumApiCode.SignWrong);
            return Content("1");
        }
  • 對修改後的test1 程式碼進行壓測結果如下:

    忘記截圖,大概和修改後的test2 程式碼壓測結果相差不大,大概tps 9300左右,故還是拿的上一個圖貼出來,諒解
介面 tps cpu佔用 壓測條件
test1 9300左右 90%左右 併發200,持續壓測
test2 9200左右 90%左右 併發200,持續壓測

進一步得到的結論是try catch 後效能和正常返回程式碼效能相當,相差無幾,可以忽略不計

2.2 最終結論

  • 異常和正常程式碼效能旗鼓相當,但是全域性過濾器對效能影響比較大,大概降低了60%左右(空業務情況下壓測,效能降低是會被放大),全域性過濾器走了管道,但是這跟微軟官方的效能優化又有衝突,想必微軟官方也是出於對全域性過濾器異常處理的考慮吧。同時對於新增了業務的情況下,這個降低會被稀釋,沒去做壓測對比哈(估計影響不會太大),正常使用者體量還不至於被這個給影響到穩定性。所以怎麼取捨看自己
  • 這裡不否定使用 全域性過濾器進行業務自定義異常捕獲,是否最外層try catch 掉還是全域性過濾器去捕獲處理,自己根據複雜度和效能兩者中自行取捨,至少全域性過濾器處理異常從效能角度上來說不是優雅的解決方式
  • 對於非自定義異常,儘量按照微軟官方建議
    • 使用 “測試者-執行者”模式
    • “嘗試-分析”模式

最後丟擲一個待求證的問題

  • 疑問一:大量丟擲非自定義異常,效能和正常返回效能對比會如何?比如字串轉換int 不使用TryParse 去轉換

以上結論個人壓測結果,如有不對,歡迎交流糾正​

相關文章