Hi,你們好。我是Java課表明。今天咱們來講一說Dubbo。java
推薦語:Dubbo做爲一款高性能的 RPC 框架,在微服務架構中普遍應用,本文基於開發過程當中的一次異常處理,深刻剖析了Dubbo 的異常處理邏輯,並結合源碼,給出了 Dubbo 異常處理的最佳實踐。
在平常業務開發過程當中,咱們爲了讓業務代碼更健壯,遇到錯誤時返回的提示更友好,通常會自定義一些業務異常。根據業務須要,分爲自定義受檢異常和非受檢異常apache
知識點回顧segmentfault
Exception
類及其子類,但不包括RuntimeException
的子類,統稱爲受檢異常。若是方法執行過程當中有可能拋出此類異常,必須在方法簽名上聲明api
RuntimeException
類及其子類,統稱爲非受檢異常。若是方法執行過程當中有可能拋出此類異常,能夠沒必要在方法簽名上聲明數組
課表明所負責的項目使用SpringCloudAlibaba
落地了微服務,開發中組內兄弟遇到一個問題:Dubbo RPC
調用時,provider
拋出的一個業務類非受檢異常,consumer
接到時倒是RuntimeException
而且message
被和堆棧信息拼接到了一塊兒。服務器
Dubbo
微服務中,provider
分爲api
和service
,consumer
只須要引入 api
從註冊中心調用service
實例便可。架構
當service
中拋出一個自定義的非受檢異常,且其相應api
包中沒有這個異常類時,就會出現異常被包裝爲RuntimeException
的狀況。框架
其實問題分析到這裏,基本就有眉目了:Dubbo
是一個RPC
框架,客戶端調用的都是遠程方法,參數和返回值都是通過序列化和反序列化爲字節數組傳輸的。consumer
必須認識這個異常才能反序列化成功。dom
很明顯,咱們拋的這個異常 Dubbo
認爲consumer
不認識,爲了不反序列化失敗,從而對異常進行了包裝。ide
下面結合源碼闡述Dubbo的異常處理機制。
Dubbo
遠程調用的異常由ExceptionFilter
類處理
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { try { Result result = invoker.invoke(invocation); if (result.hasException() && GenericService.class != invoker.getInterface()) { try { Throwable exception = result.getException(); // 若是是checked異常,直接拋出 if (! (exception instanceof RuntimeException) && (exception instanceof Exception)) { return result; } // 在方法簽名上有聲明,直接拋出 try { Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes()); Class<?>[] exceptionClassses = method.getExceptionTypes(); for (Class<?> exceptionClass : exceptionClassses) { if (exception.getClass().equals(exceptionClass)) { return result; } } } catch (NoSuchMethodException e) { return result; } // 未在方法簽名上定義的異常,在服務器端打印ERROR日誌 logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception); // 異常類和接口類在同一jar包裏,直接拋出 String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface()); String exceptionFile = ReflectUtils.getCodeBase(exception.getClass()); if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)){ return result; } // 是JDK自帶的異常,直接拋出 String className = exception.getClass().getName(); if (className.startsWith("java.") || className.startsWith("javax.")) { return result; } // 是Dubbo自己的異常,直接拋出 if (exception instanceof RpcException) { return result; } // 不然,包裝成RuntimeException拋給客戶端 return new RpcResult(new RuntimeException(StringUtils.toString(exception))); } catch (Throwable e) { logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e); return result; } } return result; } catch (RuntimeException e) { logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e); throw e; }
經過源碼能夠看到,該類的主要功能是返回接口拋出的異常,Dubbo
將其定義爲以下幾種狀況:
checked
異常,直接拋出不符合1,2 的被認爲是錯誤,會打印error日誌,並嘗試以下處理:
jar
包裏,直接拋出JDK
自帶的異常,直接拋出Dubbo
自己的異常,直接拋出RuntimeException
拋給客戶端事實上Dubbo
做爲RPC
框架已經把各類拋異常的狀況都考慮全了,最後若是Dubbo
認爲consumer
不認識這個異常還會包裝成RuntimeException
兜底,防止反序列化失敗。
若是發生了consumer
找不到provider
所拋異常的這種狀況,不客氣地講,必定是開發者的問題,把這個歸罪於Dubbo 那可就太冤枉它了!
Dubbo
官網->Dubbo 2.7->用戶文檔->服務化最佳實踐 中有以下描述:
分包
建議將服務接口、服務模型、服務異常等均放在 API 包中,由於服務模型和異常也是 API 的一部分,這樣作也符合分包原則:重用發佈等價原則(REP),共同重用原則(CRP)。
因此,符合Dubbo
最佳實踐的provider-api
中應該包含服務接口包,服務模型包,服務異常包。全部service
中用到的異常,都應該在api
包中聲明,這樣consumer
調用時纔會符合Dubbo 要求的:
異常類和接口類在同一個
jar
包裏,直接拋出
從而避免被Dubbo
包裝成RuntimeException
拋給客戶端。
因此,針對文章開頭遇到的問題,咱們只須要把provider-service
中拋出自定義的非受檢異常 在provider-api
中定義,同時在相應的方法上throw
出來就能夠了,這樣既能夠防止被Dubbo
包裝,也不會由於方法簽名中沒聲明異常而致使Dubbo
報error
錯誤。並且,由於是非受檢異常,因此也不強制客戶端對方法進行try catch
。
一個可參考的分包實踐:
+- scr | +- demo | +- domain (業務域內傳輸數據用的 DTO) | +- service (API 中 service 接口的實現類) | +- exception (業務域中的自定義異常)
若是 Google 關鍵字 [Dubbo 異常處理],你會發現幾乎全部文章都是下面這幾個思路:
ExceptionFilter
讓Dubbo
使用,兼容本身的業務異常類unchecked
異常改成checked
異常固然,上面這些方法徹底能夠解決問題,但這是否是有殺雞用牛刀的意思?
明明是代碼開發不規範,沒有遵循最佳實踐,卻要強行歸罪於底層框架。Dubbo
在努力作得通用,而上面的處理方式卻在讓代碼變得緊耦合。
總結問題本質:Dubbo
在認爲consumer
找不到異常類時,爲了防止發生反序列化失敗,對異常進行了一層包裝。針對這一實質,咱們用最簡單、高效,影響最小的辦法解決就能夠了。
課表明相信讀者結合Dubbo
異常處理的源碼,應該會有本身的判斷。
遇事不決問Google,多數狀況下咱們遇到的問題都會搜到答案,對於一樣一個問題,解決的方法可能多種多樣,咱們須要作的是找到問題的本質,觸類旁通,根據本身業務的實際狀況選擇最合適的解決方案。
切勿盲從,須知:盡信書不如無書。
【推薦閱讀】
RabbitMQ官方教程譯文
Freemarker 教程(一)-模板開發手冊
使用Spring Validation優雅地校驗參數
下載的附件名總亂碼?你該去讀一下 RFC 文檔了!
深刻淺出 MySQL 優先隊列(你必定會踩到的order by limit 問題)
碼字不易,歡迎點贊關注和分享。
搜索:【Java課表明】,關注公衆號,每日一更,及時獲取更多Java乾貨。