在微服務架構中異常如何正確使用
這篇文章跟大家介紹一下在微服務架構中異常正確使用的方法。有一定的參考價值,有需要的朋友可以參考一下,希望對大家有幫助。
異常的正確使用在微服務架構中的重要性排前三,沒什麼意見吧
異常的正確使用在微服務架構中的重要性排前三,沒什麼意見吧
Curdboy 們好久不見,先祝大家端午節快樂。最近想說說異常,我的思考儼然形成了閉環,希望這套組合拳能對你的業務代碼有所幫助。
以下只討論世界上最好的語言和生態最完整的語言,沒什麼意見吧。
異常的異同
PHP 在PHP7 異常的設計和Java 保持一致了Exception extends Throwable ,不過在歷史原因和設計理念上還是有一些細微的差別。例如 PHP 中的異常是有 code 屬性的,這樣就存在多種異常聚類為同一個異常,然後在catch 區塊裡根據 code 寫不同的業務邏輯代碼。
而 Java 例外則沒有code ,不能這樣設計,只能針對不同的情況使用不同的例外。所以我們習慣服務對外暴露的透過包裝類別來封裝,而不是直接依賴異常的透傳。
統一異常的處理
在 Java 程式碼裡,最讓人詬病的就是漫山遍野的try catch ,沒什麼意見。隨便抓一段程式碼
@Override public DataResult<List<AdsDTO>> getAds(Integer liveId) { try { List<AdsDTO> adsDTO = new ArrayList<>(); //...业务逻辑省略 DataResult.success(adsDTO); } catch (Exception e) { log.error("getAds has Exception:{}", e.getMessage(), e); DataResult.failure(ResultCode.CODE_INTERNAL_ERROR, e.getMessage()); // 将异常信息返回给服务端调用方 } return dataResult; }
很多時候都是無腦上來就先寫個 try catch 再說,不管裡面是否會有非運行時異常。比較好的方式是使用 aop 的方式來攔截所有的服務方法的調用,統一接管異常然後做處理。
@Around("recordLog()") public Object record(ProceedingJoinPoint joinPoint) throws Throwable { //... 请求调用来源记录 Object result; try { result = joinPoint.proceed(joinPoint.getArgs()); } catch (Exception e) { //... 记录异常日志 DataResult<Object> res = DataResult.failure(ResultCode.CODE_INTERNAL_ERROR, e.getMessage()); result = res; } //... 返回值日志记录 return result; }
有一點小問題,如果直接將A 服務的異常訊息直接回傳給呼叫者B,可能存在一些潛在的風險,永遠不能相信呼叫者,即使他根正苗紅三代貧農也不行。因為不能確定呼叫者會將該錯誤訊息作何處理,可能就直接作為 json 回傳給了前端。
RuntimeException
在Java 中異常可以分為運行時異常和非運行時異常,運行時異常是不需要捕獲的,在方法上也不需要標註throw Exception,例如我們在方法裡使用guava 套件裡的Preconditions工具類,拋出的IllegalArgumentException也是執行時期例外。
@Override public DataResult<List<AdsDTO>> getAds(Integer liveId) { Preconditions.checkArgument(null != liveId, "liveIds not be null"); List<AdsDTO> adsDTOS = new ArrayList<>(); //...业务逻辑省略 return DataResult.success(adsDTOS); }
我們也可以使用該特性,自訂自己的業務例外類別繼承RuntimeException
XXServiceRuntimeException extends RuntimeException
對於不符合業務邏輯情況則直接拋出XXServiceRuntimeException
@Override public DataResult<List<AdsDTO>> getAds(Integer liveId) { if (null == liveId) { throw new XXServiceRuntimeException("liveId can't be null"); } List<AdsDTO> adsDTOS = new ArrayList<>(); //...业务逻辑省略 return DataResult.success(adsDTOS); }
然後在aop 做統一處理做相應的優化,對於前面比較粗暴的做法,應該將除了XXServiceRuntimeException和IllegalArgumentException之外的異常內部記錄,不再對外暴露,但是一定要記得通過requestId將分佈式鏈路串起來,在DataResult中返回,方便問題的檢查。
@Around("recordLog()") public Object record(ProceedingJoinPoint joinPoint) throws Throwable { //... 请求调用来源记录 Object result; try { result = joinPoint.proceed(joinPoint.getArgs()); } catch (Exception e) { //... 记录异常日志① log.error("{}#{}, exception:{}:", clazzSimpleName, methodName, e.getClass().getSimpleName(), e); DataResult<Object> res = DataResult.failure(ResultCode.CODE_INTERNAL_ERROR); if (e instanceof XXServiceRuntimeException || e instanceof IllegalArgumentException) { res.setMessage(e.getMessage()); } result = res; } if (result instanceof DataResult) { ((DataResult) result).setRequestId(EagleEye.getTraceId()); // DMC } //... 返回值日志记录 return result; }
異常監控
說好的閉環呢,使用了自訂異常類別之後,對異常日誌的監控警報的閾值就可以降低不少,警報更精準,以阿里雲SLS 的監控為例
* and ERROR not XXServiceRuntimeException not IllegalArgumentException|SELECT COUNT(*) AS count
這裡監控的是記錄異常日誌① 的日誌
PHP 裡的異常
#上面Java 裡說到的問題在PHP 裡也同樣存在,不用3 種方法來模擬aop 都不能體現PHP 是世界上最好的語言
//1. call_user_func_array //2. 反射 //3. 直接 new try { $class = new $className(); $result = $class->$methodName(); } catch (\Throwable $e) { //...略 }
類似上面的架構邏輯不再重複寫偽代碼,基本上保持一致。也是自訂自己的業務異常類別繼承RuntimeException,然後做對外輸出處理。
但是PHP 裡有一些歷史包袱,起初設計的時候很多運行時異常都是作為Notice,Warning 錯誤輸出的,但是錯誤的輸出缺少調用棧,不利於問題的排查
function foo(){ return boo("xxx"); } function boo($a){ return explode($a); } foo();
Warning: explode() expects at least 2 parameters, 1 given in /Users/mengkang/Downloads/ab.php on line 8
看不到特定的參數,也看不到呼叫堆疊。如果使用set_error_handler ErrorException之後,就非常清晰了。
set_error_handler(function ($severity, $message, $file, $line) { throw new ErrorException($message, 10001, $severity, $file, $line); }); function foo(){ return boo("xxx"); } function boo($a){ return explode($a); } try{ foo(); }catch(Exception $e){ echo $e->getTraceAsString(); }
最後印出來的訊息是
Fatal error: Uncaught ErrorException: explode() expects at least 2 parameters, 1 given in /Users/mengkang/Downloads/ab.php:12 Stack trace: #0 [internal function]: {closure}(2, 'explode() expec...', '/Users/mengkang...', 12, Array) #1 /Users/mengkang/Downloads/ab.php(12): explode('xxx') #2 /Users/mengkang/Downloads/ab.php(8): boo('xxx') #3 /Users/mengkang/Downloads/ab.php(15): foo() #4 {main} thrown in /Users/mengkang/Downloads/ab.php on line 12
修改上面的函數
function boo(array $a){ return implode(",", $a); }
則沒辦法捕捉了,因為拋出的是PHP Fatal error: Uncaught TypeError,PHP7新增了
class Error implements Throwable,則在PHP 系統錯誤日誌裡會有Stack,但是不能和整個業務系統串聯起來,這裡就又不得不說日誌的設計,我們期望像Java 那樣透過一個traceId 將所有的日誌串聯起來,從Nginx 日誌到PHP 裡的正常info level 日誌以及這些Uncaught TypeError,所以接管預設輸出到系統錯誤日誌,在catch 程式碼區塊中記錄到統一的地方。那這裡就簡單修改為
set_error_handler(function ($severity, $message, $file, $line) { throw new ErrorException($message, 10001, $severity, $file, $line); }); function foo(){ return boo("xxx"); } function boo(array $a){ return implode(",", $a); } try{ foo(); }catch(Throwable $e){ echo $e->getTraceAsString(); }
catch Throwable就能接受Error和Exception了。
但是 set_error_handler 沒辦法處理一些錯誤,例如E_PARSE的錯誤,可以用register_shutdown_function來兜底。
值得注意的是register_shutdown_function的用意是在脚本正常退出或显示调用exit时,执行注册的函数。
是脚本运行(run-time not parse-time)出错退出时,才能使用。如果在调用register_shutdown_function的同一文件的里面有语法错误,是无法注册的,但是我们项目一般都是分多个文件的,这样就其他文件里有语法错误,也能捕获了
register_shutdown_function(function(){ $e = error_get_last(); if ($e){ throw new \ErrorException($e["message"], 10002, E_ERROR, $e["file"], $e["line"]); } });
如果你想直接使用这些代码(PHP的)直接到项目可能会有很多坑,因为我们习惯了系统中有很多 notice 了,可以将 notice 的错误转成异常之后主动记录,但是不对外抛出异常即可。
推荐学习:php视频教程
以上是在微服務架構中異常如何正確使用的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

PHP和Python各有優勢,選擇依據項目需求。 1.PHP適合web開發,尤其快速開發和維護網站。 2.Python適用於數據科學、機器學習和人工智能,語法簡潔,適合初學者。

PHP在電子商務、內容管理系統和API開發中廣泛應用。 1)電子商務:用於購物車功能和支付處理。 2)內容管理系統:用於動態內容生成和用戶管理。 3)API開發:用於RESTfulAPI開發和API安全性。通過性能優化和最佳實踐,PHP應用的效率和可維護性得以提升。

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7

PHP仍然具有活力,其在現代編程領域中依然佔據重要地位。 1)PHP的簡單易學和強大社區支持使其在Web開發中廣泛應用;2)其靈活性和穩定性使其在處理Web表單、數據庫操作和文件處理等方面表現出色;3)PHP不斷進化和優化,適用於初學者和經驗豐富的開發者。

PHP適合web開發,特別是在快速開發和處理動態內容方面表現出色,但不擅長數據科學和企業級應用。與Python相比,PHP在web開發中更具優勢,但在數據科學領域不如Python;與Java相比,PHP在企業級應用中表現較差,但在web開發中更靈活;與JavaScript相比,PHP在後端開發中更簡潔,但在前端開發中不如JavaScript。

PHP和Python各有優劣,選擇取決於項目需求和個人偏好。 1.PHP適合快速開發和維護大型Web應用。 2.Python在數據科學和機器學習領域佔據主導地位。

PHP和Python各有優勢,適合不同場景。 1.PHP適用於web開發,提供內置web服務器和豐富函數庫。 2.Python適合數據科學和機器學習,語法簡潔且有強大標準庫。選擇時應根據項目需求決定。

PHP主要是過程式編程,但也支持面向對象編程(OOP);Python支持多種範式,包括OOP、函數式和過程式編程。 PHP適合web開發,Python適用於多種應用,如數據分析和機器學習。
