分布式rpc异常丢失的根因是跨进程传递时被框架拦截、序列化截断与上下文剥离;需服务端保留cause链、rpc层filter透传、消费端解包重建,并结合trace_id实现全链路异常可观测。

分布式 RPC 调用中自定义异常丢失,本质是跨进程异常传递被框架拦截、序列化截断、上下文剥离所致。单纯靠 @ControllerAdvice 或 try-catch 在消费端捕获,往往失效——因为 Dubbo 等框架会把原始异常包装成 RpcException,甚至丢弃堆栈和业务语义。真正有效的解法,是把异常链(raise from / __cause__)机制从服务端贯穿到消费端,并配合链路追踪与统一异常封装。
服务端:用 throw new XxxException(..., cause) 保留原始根因
不要直接抛出裸异常,尤其避免 throw new RuntimeException("xxx")。所有自定义业务异常必须显式携带原始异常作为 cause:
- 继承
RpcException或RuntimeException,构造函数中调用super(message, cause) - 在 DAO 层或远程依赖失败处捕获底层异常(如
SQLException、TimeoutException),再封装为带 cause 的业务异常向上抛 - 示例:
throw new InsufficientBalanceException("余额不足", sqlEx);—— 此时sqlEx成为__cause__,可被后续解析
RPC 层:通过 Filter 统一封装,避免框架“吃掉”异常
Dubbo 默认会将非 RpcException 子类的异常转为泛化的 RpcException 并清空 cause。必须在服务端加自定义 Filter 拦截并透传:
- 实现
org.apache.dubbo.rpc.Filter,重写invoke(),在catch块中识别业务异常 - 对匹配的异常,调用
invocation.setAttachment("ex_code", code)和invocation.setAttachment("ex_msg", msg)将关键字段透传 - 禁止直接 throw 新异常;而是返回一个含错误信息的 Result,并设置
result.setException(...)
消费端:主动解包 + 异常链重建
消费端收到的异常大概率是 RpcException,需手动还原原始异常链:
- 在全局异常处理器中判断
exception instanceof RpcException - 读取其 attachment 中的
ex_code、ex_msg,结合已知映射表构造对应业务异常 - 若原始异常有 cause(比如服务端用
new BusinessException(..., realCause)),可在消费端用new BusinessException(msg).initCause(realCause)重建链路 - 日志打印时,用
log.error("RPC call failed", exception)—— Logback/Log4j2 默认会展开 cause 链
增强可观测性:用 trace_id 关联异常日志与链路 Span
仅靠异常链还不够,需绑定分布式上下文:
- 服务端在抛异常前,从当前
Tracer.currentSpan()获取trace_id,写入异常 message 或 attachment - 消费端捕获异常后,立即记录
span.recordException(exception),确保 OpenTelemetry 自动关联 error 事件与 span - ELK 或 Grafana 中按
trace_id聚合日志,就能看到从网关 → 用户服务 → 订单服务 → DB 失败的完整异常路径和每层的__cause__










