作者: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();
}
自定义异常信息在社区中的呼声也比较高,
因此本次改动将支持自定义异常的功能, 使得服务端能抛出自定义异常后被客户端捕获到。
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 的响应之后会调用 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;
}
Triple 通信原理
在上一节中,我们已经介绍了 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);
//异步等