关于java:Dubbo异常处理源码探究及其最佳实践

9次阅读

共计 4471 个字符,预计需要花费 12 分钟才能阅读完成。

Hi,大家好。我是Java 课代表。明天咱们来说一说 Dubbo。

举荐语:Dubbo 作为一款高性能的 RPC 框架,在微服务架构中广泛应用,本文基于开发过程中的一次异样解决,深刻分析了 Dubbo 的异样解决逻辑,并联合源码,给出了 Dubbo 异样解决的最佳实际。

1 背景

在日常业务开发过程中,咱们为了让业务代码更强壮,遇到谬误时返回的提醒更敌对,个别会自定义一些业务异样。依据业务须要,分为自定义受检异样和非受检异样

知识点回顾

Exception类及其子类,但不包含 RuntimeException 的子类,统称为受检异样。如果办法执行过程中有可能抛出此类异样,必须在办法签名上申明

RuntimeException 类及其子类,统称为非受检异样。如果办法执行过程中有可能抛出此类异样,能够不用在办法签名上申明

课代表所负责的我的项目应用 SpringCloudAlibaba 落地了微服务,开发中组内兄弟遇到一个问题:Dubbo RPC调用时,provider抛出的一个业务类非受检异样,consumer接到时却是 RuntimeException 并且message 被和堆栈信息拼接到了一起。

2 问题复现

Dubbo 微服务中,provider 分为 apiserviceconsumer只须要引入 api 从注册核心调用service 实例即可。

service 中抛出一个自定义的非受检异样,且其相应api 包中没有这个异样类时,就会出现异常被包装为 RuntimeException 的状况。

其实问题剖析到这里,根本就有眉目了:Dubbo是一个 RPC 框架,客户端调用的都是近程办法,参数和返回值都是通过序列化和反序列化为字节数组传输的。consumer必须意识这个异样能力反序列化胜利。

很显著,咱们抛的这个异样 Dubbo认为 consumer 不意识,为了防止反序列化失败,从而对异样进行了包装。

上面联合源码论述 Dubbo 的异样解决机制。

3 源码剖析

Dubbo 近程调用的异样由 ExceptionFilter 类解决

public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        try {Result result = invoker.invoke(invocation);
            if (result.hasException() && GenericService.class != invoker.getInterface()) {
                try {Throwable exception = result.getException();

                    // 如果是 checked 异样,间接抛出
                    if (! (exception instanceof RuntimeException) && (exception instanceof Exception)) {return result;}
                    // 在办法签名上有申明,间接抛出
                    try {Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
                        Class<?>[] exceptionClassses = method.getExceptionTypes();
                        for (Class<?> exceptionClass : exceptionClassses) {if (exception.getClass().equals(exceptionClass)) {return result;}
                        }
                    } catch (NoSuchMethodException e) {return result;}

                    // 未在办法签名上定义的异样,在服务器端打印 ERROR 日志
                    logger.error("Got unchecked and undeclared exception which called by" + RpcContext.getContext().getRemoteHost()
                            + ". service:" + invoker.getInterface().getName() + ", method:" + invocation.getMethodName()
                            + ", exception:" + exception.getClass().getName() + ":" + exception.getMessage(), exception);

                    // 异样类和接口类在同一 jar 包里,间接抛出
                    String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
                    String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
                    if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)){return result;}
                    // 是 JDK 自带的异样,间接抛出
                    String className = exception.getClass().getName();
                    if (className.startsWith("java.") || className.startsWith("javax.")) {return result;}
                    // 是 Dubbo 自身的异样,间接抛出
                    if (exception instanceof RpcException) {return result;}

                    // 否则,包装成 RuntimeException 抛给客户端
                    return new RpcResult(new RuntimeException(StringUtils.toString(exception)));
                } catch (Throwable e) {logger.warn("Fail to ExceptionFilter when called by" + RpcContext.getContext().getRemoteHost()
                            + ". service:" + invoker.getInterface().getName() + ", method:" + invocation.getMethodName()
                            + ", exception:" + e.getClass().getName() + ":" + e.getMessage(), e);
                    return result;
                }
            }
            return result;
        } catch (RuntimeException e) {logger.error("Got unchecked and undeclared exception which called by" + RpcContext.getContext().getRemoteHost()
                    + ". service:" + invoker.getInterface().getName() + ", method:" + invocation.getMethodName()
                    + ", exception:" + e.getClass().getName() + ":" + e.getMessage(), e);
            throw e;
        }

通过源码能够看到,该类的次要性能是返回接口抛出的异样,Dubbo将其定义为如下几种状况:

  1. 如果是 checked 异样,间接抛出
  2. 在办法签名上有申明,间接抛出
  3. 不合乎 1,2 的被认为是谬误,会打印 error 日志,并尝试如下解决:

    • 异样类和接口类在同一个 jar 包里,间接抛出
    • JDK 自带的异样,间接抛出
    • Dubbo 自身的异样,间接抛出
    • 否则,包装成 RuntimeException 抛给客户端

事实上 Dubbo 作为 RPC 框架曾经把各种抛异样的状况都思考全了,最初如果 Dubbo 认为 consumer 不意识这个异样还会包装成 RuntimeException 兜底,避免反序列化失败。

如果产生了 consumer 找不到 provider 所抛异样的这种状况,不客气地讲,肯定是开发者的问题,把这个归罪于 Dubbo 那可就太委屈它了!

4 最佳实际

Dubbo官网 ->Dubbo 2.7-> 用户文档 -> 服务化最佳实际 中有如下形容:

分包

倡议将服务接口、服务模型、服务异样等均放在 API 包中,因为服务模型和异样也是 API 的一部分,这样做也合乎分包准则:重用公布等价准则(REP),独特重用准则(CRP)。

所以,合乎 Dubbo 最佳实际的provider-api 中应该蕴含服务接口包,服务模型包,服务异样包。所有 service 中用到的异样,都应该在 api 包中申明,这样 consumer 调用时才会合乎 Dubbo 要求的:

异样类和接口类在同一个 jar 包里,间接抛出

从而防止被 Dubbo 包装成RuntimeException 抛给客户端。

所以,针对文章结尾遇到的问题,咱们只须要把 provider-service 中抛出自定义的非受检异样 在 provider-api 中定义,同时在相应的办法上 throw 进去就能够了,这样既能够避免被 Dubbo 包装,也不会因为办法签名中没申明异样而导致 Dubboerror谬误。而且,因为是非受检异样,所以也不强制客户端对办法进行try catch

一个可参考的分包实际:

  +- scr
      |
      +- demo
          |
          +- domain (业务域内传输数据用的 DTO)
          |
          +- service (API 中 service 接口的实现类)
          |
          +- exception (业务域中的自定义异样)

5 弯路

如果 Google 关键字 [Dubbo 异样解决],你会发现简直所有文章都是上面这几个思路:

  1. 自定义一个 ExceptionFilterDubbo 应用,兼容本人的业务异样类
  2. 在 provider 端写个 AOP 拦挡所有异样本人解决
  3. unchecked 异样改为 checked 异样

当然,下面这些办法齐全能够解决问题,但这是不是有杀鸡用牛刀的意思?

明明是代码开发不标准,没有遵循最佳实际,却要强行归罪于底层框架。Dubbo在致力做得通用,而下面的解决形式却在让代码变得紧耦合。

总结问题实质:Dubbo在认为 consumer 找不到异样类时,为了避免产生反序列化失败,对异样进行了一层包装。针对这一本质,咱们用最简略、高效,影响最小的方法解决就能够了。

课代表置信读者联合Dubbo 异样解决的源码,应该会有本人的判断。

6 反思

遇事不决问 Google,少数状况下咱们遇到的问题都会搜到答案,对于同样一个问题,解决的办法可能多种多样,咱们须要做的是找到问题的实质,触类旁通,依据本人业务的理论状况抉择最合适的解决方案。

切勿盲从,须知:尽信书不如无书。


【举荐浏览】
RabbitMQ 官网教程译文
Freemarker 教程 (一)- 模板开发手册
应用 Spring Validation 优雅地校验参数
下载的附件名总乱码?你该去读一下 RFC 文档了!
深入浅出 MySQL 优先队列(你肯定会踩到的 order by limit 问题)


码字不易,欢送点赞关注和分享。
搜寻:【Java 课代表】,关注公众号,每日一更,及时获取更多 Java 干货。

正文完
 0