在开发业务代码的时候,异常是主流程之外的非正常流程,是主流程进行不下去的标志,是需要进行补偿处理的。
这里表述的异常,包括非正常的 http 状态码,和异构的外部系统返回的业务异常状态码。
针对这些异常, 站在完善流程的视角上,可以进行异常归类。
第一类是网络异常:针对这类异常,我们可以进行重试,但是要注意三方系统是否幂等,幂等接口可以重试。不幂等的接口,避免重复请求,造成资损,可以再请求一遍数据前校验一下状态。
第二类是业务异常:在网络顺畅的情况下,可能由于三方原因,有些流程进行不下去,这时,三方会返回状态码,标志是否请求成功,针对三方业务异常状态码,还可以进行一个归类。
基于上述分类,我们完善流程时,就有了多种条件,可以针对性的进行三方下架、重试、标记异常请求等。
如果是有多个相同能力的三方系统,还可以对三方系统的稳定性等进行打标,针对指标进行运营。
在写流程代码时,可以使用 微内核加插件化,平等对待第三方的设计思想。或者更进行一步,使用状态机进行流程编排,把固定的内核流程做成可以插拨的形式。
首先 java.lang.Throwable 就是一个java类,没有什么特别的地方,之所以异常能够跳出链式调用流程,直接抛出,还是因为虚拟机的原因,因为 throw 关键字。
public static void main(String[] args) {
try {
mayThrowException();
} catch (Exception e) {
e.printStackTrace();
}
}
// 对应的 Java 字节码
public static void main(java.lang.String[]);
Code:
0: invokestatic mayThrowException:()V
3: goto 11
6: astore_1
7: aload_1
8: invokevirtual java.lang.Exception.printStackTrace
11: return
Exception table:
from to target type
0 3 6 Class java/lang/Exception // 异常表条目
12345678910111213141516171819
附上一个编译后的带有 try catch 的字节码表,可以看出,编译后的字节码,每个方法都附带一个异常表,并且由 from、to、target 指针构成,这些指针的值,是 字节码索引。
from to 代表了监控的范围,targe 代表异常处理器的起始位置。
如果遍历完所有的一场表条目,仍未找到正确的异常处理器,那么会弹出当前方法的栈帧,并且在调用者中重复此操作。
虚拟机除了找到正常的异常处理流程外,还会构造生成该异常的栈轨迹。该操会逐一访问当前线程的 java 栈帧,并记录调试信息,包括 栈帧指向方法的名字、方法所在的类名、文件名、以及在代码中的第几行触发了该异常。所以异常实例的构建十分昂贵。
虚拟机异常其实就是同构系统对异常的处理。
rpc 的基本流程 序列化-》网络传输-》反序列化。这是基本的流程,如果出现异常了,那么经过网络传输,然后经过反序列化,还会生成原来的对象吗?
Throwable 实现了 Serializable 接口,表明异常也是可以被序列化的。
不同的 rpc 框架有不同的处理方式。
dubbo 就是 com.alibaba.dubbo.rpc.filter.ExceptionFilter,这个类中对不同的异常有不同的处理方式,受检异常直接抛出,其他异常,原则上如果调用方是可以进行反序列化为 java对象 的就直接抛出,如果不能,则使用 RuntimeException 包裹后再抛出。
feign 默认是通过 http 协议进行网络传输。所以如果被调用方抛异常了,返回给调用方的,就不是异常对象,而是异常信息。springboot web 项目,异常时返回的信息就是 status、timestamp、message、error、path,仅通过这些信息,是没法反序列化成 java 对象的。所以 feign 的异常处理,是获取 message 信息,然后包装成 FeignException 返回出去。
因为 dubbo 框架抛出的异常信息中,有类信息,所以存在反序列化的可能。一般网络传输框架为了多语言通用,基本没有 java 的类信息,所以反序列化不回来。