- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
作者: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 的回传异常会在 AbstractInvoker 的 waitForResultIfSync 中把异常信息堆栈统一封装成 RpcException , 所有来自 Provider 端的异常都会被封装成 RpcException 类型并抛出, 这会导致用户无法根据特定的异常类型捕获来自 Provider 的异常, 只能通过捕获 RpcException 异常来返回信息, 且 Provider 携带的异常 message 也无法回传,只能获取打印的堆栈信息:
try {
greeterProxy.echo(REQUEST_MSG);
} catch (RpcException e) {
e.printStackTrace();
}
自定义异常信息在社区中的呼声也比较高, 因此本次改动将支持自定义异常的功能, 使得服务端能抛出自定义异常后被客户端捕获到.
我们从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 的响应之后会调用 appResponse 到 recreate 方法,若 appResponse 里包含异常, 则会抛出给用户,大体流程如下:
上述的异常处理相关环节是在 Consumer 端,在 Provider 端则是由 org.apache.dubbo.rpc.filter.ExceptionFilter 进行处理, 它是一系列责任链 Filter 中的一环,专门用来处理异常.
Dubbo 在 Provider 端的异常会在封装进 appResponse 中。下面的流程图揭示了 ExceptionFilter 源码的异常处理流程
而当 appResponse 回到了 Consumer 端,会在 InvocationUtil 里调用 AppResponse 的 recreate 方法抛出异常, 最终可以在 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;
}
在上一节中,我们已经介绍了 Dubbo 在 Consumer 端大致发送数据的流程, 可以看到最终依靠的是 AbstractInvoker 的实现类来发送数据。 在 Triple 协议中, AbstractInvoker 的具体实现类是 TripleInvoker , TripleInvoker 在发送前会启动监听器,监听来自 Provider 端的响应结果, 并调用 ClientCallToObserverAdapter 的 onNext 方法发送消息, 最终会在底层封装成 Netty 请求发送数据.
在正式的请求发起前,TripleServer 会注册 TripleHttp2FrameServerHandler , 它继承自 Netty 的 ChannelDuplexHandler , 其作用是会在 channelRead 方法中不断读取 Header 和 Data 信息并解析, 经过层层调用, 会在 AbstractServerCall 的 onMessage 方法里把来自 consumer 的信息流进行反序列化, 并最终由交由 ServerCallToObserverAdapter 的 invoke 方法进行处理.
在 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());
});
}
...
}
大体流程如下:
了解了上述原理,我们就可以进行相应的改造了, 能让 consumer 端捕获异常的 关键在于把异常对象以及异常信息序列化后再发送给consumer端 。 常见的序列化协议很多,例如 Dubbo/HSF 默认的 hessian2 序列化; 还有使用广泛的 JSON 序列化;以及 gRPC 原生支持的 protobuf(PB) 序列化等等。 Triple协议因为兼容grpc的原因,默认采用 Protobuf 进行序列化。 上述提到的这三种典型的序列化方案作用类似,但在实现和开发中略有不同。 PB 不可由序列化后的字节流直接生成内存对象, 而 Hessian 和 JSON 都是可以的。后两者反序列化的过程不依赖“二方包”, 其序列化和反序列化的代码由 proto 文件相同,只要客户端和服务端用相同的 proto 文件进行通信, 就可以构造出通信双方可解析的结构.
单一的 protobuf 无法序列化异常信息, 因此我们采用 Wrapper + PB 的形式进行序列化异常信息, 抽象出一个 TripleExceptionWrapperUtils 用于序列化异常, 并在 trailer 中采用 TripleExceptionWrapperUtils 序列化异常,大致代码流程如下:
上面的实现方案看似非常合理,已经能把 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层去支持序列化异常就能满足需求?
首先回答第2、三个问题: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端进行解析和反序列化,大体流程如下:
通过对 Dubbo 3.0 新增自定义异常的版本迭代中可以看出,尽管只能新增一个小小的特性,流程下并不复杂,但由于要考虑互通、兼容和协议的设计理念,因此思考和讨论的时间可能比写代码的时间更多.
欢迎在 https://github.com/apache/dubbo 给 Dubbo Star。 搜索关注官方微信公众号:Apache Dubbo,了解更多业界最新动态,掌握大厂面试必备 Dubbo 技能 。
最后此篇关于Triple协议支持Java异常回传的设计与实现的文章就讲到这里了,如果你想了解更多关于Triple协议支持Java异常回传的设计与实现的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
问题很简单:我正在寻找一种优雅的使用方式 CompletableFuture#exceptionally与 CompletableFuture#supplyAsync 一起.这是行不通的: priva
对于 Web 服务,我们通常使用 maven-jaxb2-plugin 生成 java bean,并在 Spring 中使用 JAXB2 编码。我想知道如何处理 WSDL/XSD 中声明的(SOAP-
这个问题已经有答案了: Array index out of bound behavior (10 个回答) 已关闭 8 年前。 我对下面的 C 代码感到好奇 int main(){
当在类的开头使用上下文和资源初始化 MediaPlayer 对象时,它会抛出 NullPointer 异常,但是当在类的开头声明它时(因此它是 null),然后以相同的方式初始化它在onCreate方
嘿 我尝试将 java 程序连接到 REST API。 使用相同的代码部分,我在 Java 6 中遇到了 Java 异常,并且在 Java 8 中运行良好。 环境相同: 信任 机器 unix 用户 代
我正在尝试使用 Flume 和 Hive 进行 Twitter 分析。为了从 twitter 获取推文,我在 flume.conf 文件中设置了所有必需的参数(consumerKey、consumer
我在 JavaFX 异常方面遇到一些问题。我的项目在我的 Eclipse 中运行,但现在我的 friend 也尝试访问该项目。我们已共享并直接保存到保管箱文件夹中。但他根本无法让它发挥作用。他在控制台
假设我使用 blur() 事件验证了电子邮件 ID,我正在这样做: $('#email').blur(function(){ //make ajax call , check if dupli
我这样做是为了从 C 代码调用非托管函数。 pCallback 是一个函数指针,因此在托管端是一个委托(delegate)。 [DllImport("MyDLL.dll")] public stati
为什么这段代码是正确的: try { } catch(ArrayOutOfBoundsException e) {} 这是错误的: try { } catch(IOException e) {} 这段
我遇到了以下问题:有导出函数的DLL。 代码示例如下:[动态链接库] __declspec(dllexport) int openDevice(int,void**) [应用] 开发者.h: __de
从其他线程,我知道我们不应该在析构函数中抛出异常!但是对于下面的例子,它确实有效。这是否意味着我们只能在一个实例的析构函数中抛出异常?我们应该如何理解这个代码示例! #include using n
为什么需要异常 引出 public static void main(String[
1. Java的异常机制 Throwable类是Java异常类型的顶层父类,一个对象只有是 Throwable 类的(直接或者间接)实例,他才是一个异常对象,才能被异常处理机制识别。JDK中内
我是 Python 的新手,我对某种异常方法的实现有疑问。这是代码(缩写): class OurException(Exception): """User defined Exception"
我已经创建了以下模式来表示用户和一组线程之间的关联,这些线程按他们的最后一条消息排序(用户已经阅读了哪些线程,哪些没有): CREATE TABLE table(user_id bigint, mes
我正在使用 Python 编写一个简单的自动化脚本,它可能会在多个位置引发异常。在他们每个人中,我都想记录一条特定的消息并退出程序。为此,我在捕获异常并处理它(执行特定的日志记录操作等)后引发 Sys
谁能解释一下为什么这会导致错误: let xs = [| "Mary"; "Mungo"; "Midge" |] Array.iter printfn xs 虽然不是这样: Array.iter pr
在我使用 Play! 的网站上,我有一个管理部分。所有 Admin Controller 都有一个 @With 和一个 @Check 注释。 断开连接后,一切正常。连接后,每次加载页面(任何页面,无论
我尝试连接到 azure 表存储并添加一个对象。它在本地主机上工作得很好,但是在我使用的服务器上我得到以下异常及其内部异常: Exception of type 'Microsoft.Wind
我是一名优秀的程序员,十分优秀!