Triple 協議支持 Java 異常回傳的設計與實現

来源:https://www.cnblogs.com/apache-dubbo/archive/2022/12/29/17011954.html
-Advertisement-
Play Games

摘要:本文主要講解ACE去霧演算法、暗通道先驗去霧演算法以及霧化生成演算法。 本文分享自華為雲社區《[Python圖像處理] 三十.圖像預處理之圖像去霧詳解(ACE演算法和暗通道先驗去霧演算法)丨【拜托了,物聯網!】》,作者:eastmount 。 一.圖像去霧 隨著社會的發展,環境污染逐漸加劇,越來越多的城 ...


作者:Apache Dubbo Contributor 陳景明

背景

在一些業務場景, 往往需要自定義異常來滿足特定的業務, 主流用法是在catch里拋出異常, 例如:

public void deal() {
  try{
   //doSomething   
   ...
  } catch(IGreeterException e) {
      ...
      throw e;
  }   
}

或者通過ExceptionBuilder,把相關的異常對象返回給consumer:

provider.send(new ExceptionBuilders.IGreeterExceptionBuilder()
    .setDescription('異常描述信息'); 

在拋出異常後, 通過捕獲和instanceof來判斷特定的異常, 然後做相應的業務處理,例如:

try {
    greeterProxy.echo(REQUEST_MSG);
} catch (IGreeterException e) {
    //做相應的處理
    ...
}

在 Dubbo 2.x 版本,可以通過上述方法來捕獲 Provider 端的異常。
而隨著雲原生時代的到來, Dubbo 也開啟了 3.0 的里程碑。

Dubbo 3.0 的一個很重要的目標就是全面擁抱雲原生,
在 3.0 的許多特性中,很重要的一個改動就是支持新的一代Rpc協議Triple

Triple 協議基於 HTTP 2.0 進行構建,對網關的穿透性強,相容 gRPC
提供 Request Response、Request Streaming、Response Streaming、
Bi-directional Streaming 等通信模型;
從 Triple 協議開始,Dubbo 還支持基於 IDL 的服務定義。

採用 Triple 協議的用戶可以在 provider 端生成用戶定義的異常信息,
記錄異常產生的堆棧,triple 協議可保證將用戶在客戶端獲取到異常的message。

Triple 的回傳異常會在 AbstractInvokerwaitForResultIfSync
中把異常信息堆棧統一封裝成 RpcException
所有來自 Provider 端的異常都會被封裝成 RpcException 類型並拋出,
這會導致用戶無法根據特定的異常類型捕獲來自 Provider 的異常,
只能通過捕獲 RpcException 異常來返回信息,
且 Provider 攜帶的異常 message 也無法回傳,只能獲取列印的堆棧信息:

    try {
        greeterProxy.echo(REQUEST_MSG);
    } catch (RpcException e) {
        e.printStackTrace();
    }

自定義異常信息在社區中的呼聲也比較高,
因此本次改動將支持自定義異常的功能, 使得服務端能拋出自定義異常後被客戶端捕獲到。

Dubbo異常處理簡介

我們從Consumer的角度看一下一次Triple協議 Unary請求的大致流程:

Dubbo Consumer 從 Spring 容器中獲取 bean 時獲取到的是一個代理介面,
在調用介面的方法時會通過代理類遠程調用介面並返回結果。

Dubbo提供的代理工廠類是 ProxyFactory,通過 SPI 機制預設實現的是 JavassistProxyFactory
JavassistProxyFactory 創建了一個繼承自 AbstractProxyInvoker 類的匿名對象,
並重寫了抽象方法 doInvoke
重寫後的 doInvoke 只是將調用請求轉發給了 Wrapper 類的 invokeMethod 方法,
並生成 invokeMethod 方法代碼和其他一些方法代碼。

代碼生成完畢後,通過 Javassist 生成 Class 對象,
最後再通過反射創建 Wrapper 實例,隨後通過 InvokerInvocationHandler -> InvocationUtil -> AbstractInvoker -> 具體實現類發送請求到Provider端。

Provider 進行相應的業務處理後返回相應的結果給 Consumer 端,來自 Provider 端的結果會被封裝成 AsyncResult ,在 AbstractInvoker 的具體實現類里,
接受到來自 Provider 的響應之後會調用 appResponserecreate 方法,若 appResponse 里包含異常,
則會拋出給用戶,大體流程如下:

1.jpeg

上述的異常處理相關環節是在 Consumer 端,在 Provider 端則是由 org.apache.dubbo.rpc.filter.ExceptionFilter 進行處理,
它是一系列責任鏈 Filter 中的一環,專門用來處理異常。

Dubbo 在 Provider 端的異常會在封裝進 appResponse 中。下麵的流程圖揭示了 ExceptionFilter 源碼的異常處理流程:

2.jpeg

而當 appResponse 回到了 Consumer 端,會在 InvocationUtil 里調用 AppResponserecreate 方法拋出異常,
最終可以在 Consumer 端捕獲:

public Object recreate() throws Throwable {
    if (exception != null) {
    try {
        Object stackTrace = exception.getStackTrace();
        if (stackTrace == null) {
            exception.setStackTrace(new StackTraceElement[0]);
        }
    } catch (Exception e) {
        // ignore
    }
    throw exception;
}
return result;
}

Triple 通信原理

在上一節中,我們已經介紹了 Dubbo 在 Consumer 端大致發送數據的流程,
可以看到最終依靠的是 AbstractInvoker 的實現類來發送數據。
在 Triple 協議中,AbstractInvoker 的具體實現類是 TripleInvoker
TripleInvoker 在發送前會啟動監聽器,監聽來自 Provider 端的響應結果,
並調用 ClientCallToObserverAdapteronNext 方法發送消息,
最終會在底層封裝成 Netty 請求發送數據。

在正式的請求發起前,TripleServer 會註冊 TripleHttp2FrameServerHandler
它繼承自 Netty 的 ChannelDuplexHandler
其作用是會在 channelRead 方法中不斷讀取 Header 和 Data 信息並解析,
經過層層調用,
會在 AbstractServerCallonMessage 方法里把來自 consumer 的信息流進行反序列化,
並最終由交由 ServerCallToObserverAdapterinvoke 方法進行處理。

invoke 方法中,根據 consumer 請求的數據調用服務端相應的方法,並非同步等待結果;'
若服務端拋出異常,則調用 onError 方法進行處理,
否則,調用 onReturn 方法返回正常的結果,大致代碼邏輯如下:

public void invoke() {
    ...
    try {
        //調用invoke方法請求服務
        final Result response = invoker.invoke(invocation);
        //非同步等待結果
        response.whenCompleteWithContext((r, t) -> {
            //若異常不為空
            if (t != null) {
                //調用方法過程出現異常,調用onError方法處理
                responseObserver.onError(t);
                return;
            }
            if (response.hasException()) {
                //調用onReturn方法處理業務異常
                onReturn(response.getException());
                return;
            }
            ...
            //正常返回結果
            onReturn(r.getValue());
        });
    } 
    ...
}

大體流程如下:

3.jpeg

實現版本

瞭解了上述原理,我們就可以進行相應的改造了,
能讓 consumer 端捕獲異常的關鍵在於把異常對象以及異常信息序列化後再發送給consumer端
常見的序列化協議很多,例如 Dubbo/HSF 預設的 hessian2 序列化;
還有使用廣泛的 JSON 序列化;以及 gRPC 原生支持的 protobuf(PB) 序列化等等。
Triple協議因為相容grpc的原因,預設採用 Protobuf 進行序列化。
上述提到的這三種典型的序列化方案作用類似,但在實現和開發中略有不同。
PB 不可由序列化後的位元組流直接生成記憶體對象,
而 Hessian 和 JSON 都是可以的。後兩者反序列化的過程不依賴“二方包”,
其序列化和反序列化的代碼由 proto 文件相同,只要客戶端和服務端用相同的 proto 文件進行通信,
就可以構造出通信雙方可解析的結構。

單一的 protobuf 無法序列化異常信息,
因此我們採用 Wrapper + PB 的形式進行序列化異常信息,
抽象出一個 TripleExceptionWrapperUtils 用於序列化異常,
併在 trailer 中採用 TripleExceptionWrapperUtils 序列化異常,大致代碼流程如下:

4.jpeg

上面的實現方案看似非常合理,已經能把 Provider 端的異常對象和信息回傳,
併在 Consumer 端進行捕獲。但仔細想想還是有問題的:
通常在 HTTP2 為基礎的通信協議里會對 header 大小做一定的限制,
太大的header size 會導致性能退化嚴重,為了保證性能,
往往以 HTTP2 為基礎的協議在建立連接的時候是要協商最大 header size 的,
超過後會發送失敗。對於 Triple 協議來說,在設計之初就是基於 HTTP 2.0,
能無縫相容 Grpc,而 Grpc header 頭部只有 8KB 大小,
異常對象大小可能超過限制,從而丟失異常信息;
且多一個 header 攜帶序列化的異常信息意味著用戶能加的 header 數量會減少,
擠占了其他 header 所能占用的空間。

經過討論,考慮將異常信息放置在 Body,將序列化後的異常從 trailer 挪至 body,
採用 TripleWrapper + protobuf 進行序列化,把相關的異常信息序列化後回傳。
社區圍繞這個問題進行了一系列的爭論,讀者也可嘗試先思考一下:

1.在 body 中攜帶回傳的異常信息,其對應HTTP header狀態碼該設置為多少?

2.基於 http2 構建的協議,按照主流的 grpc 實現方案,相關的錯誤信息放在 trailer,理論上不存在body,上層協議也需要保持語義一致性,若此時在payload回傳異常對象,且grpc並沒有支持在Body回傳序列化對象的功能, 會不會破壞Http和grpc協議的語義?從這個角度出發,異常信息更應該放在trailer里。

3.作為開源社區,不能一味滿足用戶的需求,非標準化的用法註定是會被淘汰的,應該儘量避免更改 Protobuf的語義,是否在Wrapper層去支持序列化異常就能滿足需求?

首先回答第二、三個問題:HTTP 協議並沒有約定在狀態碼非 2xx 的時候不能返回 body,返回之後是否讀取取決於用戶。grpc 採用protobuf進行序列化,所以無法返回 exception;且try catch機製為java獨有,其他語言並沒有對應的需求,但Grpc暫時不支持的功能並一定是unimplemented,Dubbo的設計目標之一是希望能和主流協議甚至架構進行對齊,但對於用戶合理的需求也希望能進行一定程度的修改。且從throw本身的語義出發,throw 的數據不只是一個 error message,序列化的異常信息帶有業務屬性,根據這個角度,更不應該採用類似trailer的設計。至於單一的Wrapper層,也沒辦法和grpc進行互通。至於Http header狀態碼設置為200,因為其返回的異常信息已經帶有一定的業務屬性,不再是單純的error,這個設計也與grpc保持一致,未來考慮網關採集可以增加新的triple-status。

更改後的版本只需在異常不為空時返回相關的異常信息,採用 TripleWrapper + Protobuf 進行序列化異常信息,併在consumer端進行解析和反序列化,大體流程如下:

5.jpeg

總結

通過對 Dubbo 3.0 新增自定義異常的版本迭代中可以看出,儘管只能新增一個小小的特性,流程下並不複雜,但由於要考慮互通、相容和協議的設計理念,因此思考和討論的時間可能比寫代碼的時間更多。

歡迎在 https://github.com/apache/dubbo 給 Dubbo Star。
搜索關註官方微信公眾號:Apache Dubbo,瞭解更多業界最新動態,掌握大廠面試必備 Dubbo 技能


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 家居網購項目實現010 以下皆為部分代碼,詳見 https://github.com/liyuelian/furniture_mall.git 24.bugFix-添加購物車按鈕動態處理 24.1需求分析/圖解 如某個家居的庫存量為0,前臺的“add to cart”按鈕顯示為“暫時缺貨” 後臺也要 ...
  • 1. C++ 98/03標準的for迴圈 在C++ 98/03標準中,如果要用 for 迴圈語句遍歷一個數組或者容器,只能套用如下結構: for(表達式 1; 表達式 2; 表達式 3){ //迴圈體 } 例如,下麵程式演示了用上述結構遍曆數組和容器的具體實現過程: #include <iostre ...
  • Python 是每個程式員都喜歡的語言,因為它易於編碼和易於閱讀的語法。但是,你知道 python 有一些很酷的技巧可以用來讓事情變得更簡單嗎?在今天的內容中,我將與你分享7 個你可能從未使用過的Python 技巧。 1、功能屬性 這種 hack 類似於類和對象概念。現在,可以聲明,也可以稍後在程式 ...
  • JZ67 把字元串轉換成整數(atoi) 題目 寫一個函數 StrToInt,實現把字元串轉換成整數這個功能。不能使用 atoi 或者其他類似的庫函數。傳入的字元串可能有以下部分組成: 1.若幹空格 2.(可選)一個符號字元('+' 或 '-') 3. 數字,字母,符號,空格組成的字元串表達式 4. ...
  • 1、阻塞 阻塞模式下,相關方法都會導致線程暫停 ServerSocketChannel.accept 會在沒有連接建立時讓線程暫停 SocketChannel.read 會在通道中沒有數據可讀時讓線程暫停 阻塞的表現其實就是線程暫停了,暫停期間不會占用 cpu,但線程相當於閑置 單線程下,阻塞方法之 ...
  • 用python爬取並分析《2021胡潤百富榜》的榜單數據! 1、python爬蟲講解(requests向介面請求)。 2、python數據分析講解(pandas數據分析及可視化畫圖)含:直方圖、柱形圖、餅圖、詞雲圖等。 ...
  • 安裝anaconda,進行數據標註 1.安裝前準備:下好安裝包和所需文件 https://www.aliyundrive.com/s/XyH2JQ5TjCz 提取碼: 3c2w 2.運行anaconda安裝包,解壓labelimg-master文件 3.把resources.py文件放到/label ...
  • 這篇文章更進一步,會結合電商前後臺API系統,把Go語言的知識點應用到商業項目中,讓大家結合實際的場景去理解,這樣應該對大家更有幫助! ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...