2
头图

Hi, everyone. I am Java class representative . Today we will talk about Dubbo.

Recommendation: As a high-performance RPC framework, Dubbo is widely used in microservice architecture. Based on an exception handling in the development process, this article analyzes Dubbo's exception handling logic in depth, and combines the source code to give Dubbo exception handling Best practices.

1 background

In the daily business development process, we generally customize some business exceptions in order to make the business code more robust, and the prompts returned when errors are encountered are more friendly. According to business needs, it is divided into custom checked exceptions and unchecked exceptions

Knowledge point review

Exception class and its subclasses, but not RuntimeException , are collectively referred to as checked exceptions. If such an exception is likely to be thrown during the execution of the method, it must be declared on the method signature

RuntimeException category and its subcategories are collectively referred to as unchecked exceptions. If this type of exception may be thrown during the execution of the method, it is not necessary to declare on the method signature

The project in charge of the class representative used SpringCloudAlibaba land the microservices. During the development, the brothers in the group encountered a problem: When Dubbo RPC called, a business class provider consumer , 060f4ce9a0daf3 received RuntimeException and message The stack information is spliced together.

2 Problem recurring

Dubbo , provider divided into api and service . consumer only needs to introduce api service instance from the registry.

When service is thrown in api package, the exception will be packaged as RuntimeException .

In fact, when the problem is analyzed, there is basically RPC Dubbo is a 060f4ce9a0db9f framework. The client calls all remote methods, and the parameters and return values are serialized and deserialized for byte array transmission. consumer must recognize this exception in order to deserialize successfully.

Obviously, we throw the exception Dubbo that consumer does not recognize it. In order to avoid deserialization failure, the exception is wrapped.

The following describes Dubbo's exception handling mechanism in conjunction with the source code.

3 Source code analysis

Dubbo remote call exceptions are handled 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;
        }

As you can see from the source code, the main function of this class is to return the exception thrown by the interface. Dubbo defines it as the following situations:

  1. If it is checked exception, throw directly
  2. There is a statement on the method signature, throw directly
  3. Those that do not meet 1, 2 are considered errors, and the error log will be printed, and the following processing will be tried:

    • The exception class and the interface class are in the same jar package, throw directly
    • It is an exception that comes with JDK
    • It is Dubbo itself, thrown directly
    • Otherwise, pack it into RuntimeException throw it to the client

In fact, Dubbo as the RPC framework, has considered all kinds of exceptions. Finally, if Dubbo thinks that consumer does not recognize this exception, it will be packaged into RuntimeException prevent deserialization from failing.

If this happens consumer not find provider by throwing an exception in this case, put it bluntly, it must be a problem for developers, put the blame Dubbo It would be very wrong of it!

4 best practices

Dubbo official website ->Dubbo 2.7->User documentation->Serving best practice has the following description:

Subcontract

It is recommended to put service interfaces, service models, service exceptions, etc. in the API package, because the service model and exceptions are also part of the API. This is also in line with the principle of subcontracting: Reuse Release Equivalence Principle (REP), Common Reuse Principle (CRP) ).

Dubbo , which conforms to the best practice of provider-api , should include the service interface package, service model package, and service exception package. All service used in abnormal, should be in api statement package, such consumer will meet the requirements of Dubbo when you call:

Exception class and interface class are in the same jar package, throw directly

So as to avoid being packaged Dubbo RuntimeException to the client.

So, for the problems encountered at the beginning of the article, we just need to provider-service thrown Unchecked custom exception provider-api definition, while in the corresponding methods throw out on it, both to prevent Dubbo packaging, Dubbo report a error error because there is no exception declared in the method signature. try catch exception, the client is not forced to perform 060f4ce9a0e17e on the method.

A reference subcontracting practice:

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

5 Detours

If Google keyword [Dubbo exception handling], you will find that almost all articles are based on the following ideas:

  1. Customize a ExceptionFilter for Dubbo use, compatible with your own business exception class
  2. Write an AOP on the provider side to intercept all exceptions and handle them yourself
  3. unchecked exception to checked exception

Of course, the above methods can completely solve the problem, but does it mean to use a sledgehammer?

Obviously the code development is not standardized, and the best practice is not followed, but the underlying framework is forcibly blamed. Dubbo is trying to be universal, but the above processing method is making the code tightly coupled.

Summarize the essence of the problem: Dubbo thought that consumer could not find the exception class, in order to prevent the deserialization failure, the exception was packaged. In view of this essence, we can solve it with the simplest, efficient, and least impactful method.

The class representative believes that readers should have their own judgment in Dubbo

6 reflection

Ask Google if something goes wrong. In most cases, we will find the answer to the problem we encounter. For the same problem, there may be many ways to solve it. What we need to do is to find the essence of the problem, draw inferences from one another, and based on the actual situation of our business. Choose the most suitable solution for the situation.

Don't follow blindly. Note: It is better to have no books than nothing.


[Recommended reading]
RabbitMQ official tutorial translation
Freemarker Tutorial (1)
uses Spring Validation to elegantly validate parameters
downloaded attachment name is always garbled? You should read the RFC document!
MySQL priority queue in a simple way (the order by limit problem you will definitely step on)


The codeword is not easy, welcome to like, follow and share.
Search: [ Java class representative ], follow the official account, update every day, and get more Java dry goods in time.


Java课代表
640 声望1k 粉丝

JavaWeb一线开发,5年编程经验