Dubbo异常处理机制以及应用


背景

  我们的项目使用了Dubbo进行不同系统服务间的调用,当服务端发生异常时,我们希望把异常传递给消费端,由消费端对异常进行捕获并处理。但在实际使用中,发现以往的异常处理在dubbo服务中并不能奏效。例如,自定义异常类BizException继承RuntimeException,当服务端抛出这个异常时,消费端并不能捕获它。

//自定义BizException
CLass BizException extends RuntimeException{
...
}

//服务端提供方法
public void provider(){
   ...//抛出BizException
}

//消费端
public void consumer(){
try{
    provider();//dubbo调用provider方法
}
catch(BizException){
      ...//捕获异常处理
}

}

 

Dubbo异常处理机制

  当Dubbo的provider端抛出异常(Throwable),会被provider端的ExceptionFilter拦截到,执行以下invoke方法: (源码位置:com.alibaba.dubbo.rpc.filter.ExceptionFilter)

/*
 * Copyright 1999-2011 Alibaba Group.
 *  
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.dubbo.rpc.filter;

import java.lang.reflect.Method;

import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.common.logger.Logger;
import com.alibaba.dubbo.common.logger.LoggerFactory;
import com.alibaba.dubbo.common.utils.ReflectUtils;
import com.alibaba.dubbo.common.utils.StringUtils;
import com.alibaba.dubbo.rpc.Filter;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Result;
import com.alibaba.dubbo.rpc.RpcContext;
import com.alibaba.dubbo.rpc.RpcException;
import com.alibaba.dubbo.rpc.RpcResult;
import com.alibaba.dubbo.rpc.service.GenericService;

/**
 * ExceptionInvokerFilter
 * <p>
 * 功能:
 * <ol>
 * <li>不期望的异常打ERROR日志(Provider端)<br>
 *     不期望的日志即是,没有的接口上声明的Unchecked异常。
 * <li>异常不在API包中,则Wrap一层RuntimeException。<br>
 *     RPC对于第一层异常会直接序列化传输(Cause异常会String化),避免异常在Client出不能反序列化问题。
 * </ol>
 * 
 * @author william.liangf
 * @author ding.lid
 */
@Activate(group = Constants.PROVIDER)
public class ExceptionFilter implements Filter {

    private final Logger logger;
    
    public ExceptionFilter() {
        this(LoggerFactory.getLogger(ExceptionFilter.class));
    }
    
    public ExceptionFilter(Logger logger) {
        this.logger = logger;
    }
    
    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;
        }
    }

}
ExceptionFilter

  1、调用结果有异常且未实现GenericService接口,进入后续判断逻辑,否则直接返回结果

  2、不是RuntimeException类型的异常,并且是受检Checked异常(继承Exception),直接抛出

  3、在方法签名上声明了异常,直接抛出

  4、未在方法签名上定义的异常,在服务端打印ERROR日志

  5、异常类和接口类在同一jar包里,直接抛出

  6、JDK自带的异常(以java.或javax.开头)直接抛出

  7、Dubbo本身的异常,直接抛出(RpcException)

  8、不满足上述条件时会被包装成RuntimeException抛给consumer端

  由此可见,我们定义在公共包中的BizException在provider端抛出的时候会被Dubbo的异常过滤包装成RuntimeException抛给consumer端,而consumer端捕获的是BizException,所以并不会捕获到它。

解决方法

  1、provider端实现GenericService接口:

  缺点:需要自己实现invoke方法。

  2、在provider端方法签名上显式声明异常:

public void providerFunctionA(String str) throws BizException

  缺点:代码侵入性高,基本所有接口都要写。

  3、将自定义异常的包名以java.或javax.开头

package java.common.core
Class BizException extends RuntimeException{
...
}

  缺点:不满足项目命名规范。

  4、自定义异常声明为受检异常:

//继承Exception
Class BizException1 extends Exception{
...
}
Class BizException2 extends Exception{
...
}
//服务端方法中抛出
function() throws BizException1,BizException2{
...
}

  缺点:项目规模扩大后,受检异常过多会难以处理且同2情况代码侵入性高。

  5、将异常类与接口类放在同一个jar包中。

最终方案

  这里采用方案5(将异常类与接口类放在同一个jar包中)作为最终方案,注意使用过程中应避免存在链式调用。

      1、在公共包中自定义父类异常,继承RuntimeException

package com.xxx.common.core

Class BizException extends RuntimeExceptio{
    ...
}

      2、各个provider端定义子类异常,继承BizException并与接口放在同个工程中

//放于服务端facade接口工程中
package com.xxx.user.facade.exceptions
//子类异常
Class UserBizException extends BizException{
...
}

      3、各个provider端方法抛出自己模块的子类异常

package com.xxx.user.facade.service.impl

Class ProviderServiceImpl{
    //服务提供方法实现类
    public void provider(){
         ...
         throw UserBizException.XXXXXX;//抛出子类异常
         ...
     }

}

      4、consumer端统一捕获父类异常BizException

//消费端方法
void consumer(){
  try{
     providerService.provider();//调用服务端方法
  }
  catch(BizException e){
     handleException();//异常捕获处理
  }
}

  至此,provider端抛出的自定义异常可传递到consumer端被捕获处理。但是在使用中我们还会发现一个问题,那就是当provider端抛出其他的非自定义异常(例如Dubbo自身的RpcException、系统其它运行时异常等)时,consumer端并没能捕获到进行后续处理。所以,这里添加一个拦截器来确保各个provider端抛出的异常都是自己模块下的自定义异常。

      5、新增一个aop切面

<bean id="userBizExceptionFilter" class="com.xxx.user.facade.exceptions.UserBizExceptionFilter" />
<aop:config>
    <aop:aspect ref="userBizExceptionFilter">
        <aop:pointcut id="userBizExceptionAspect"
              expression="execution(* com.xxx.user.facade.service..*.*(..))"/>
        <aop:after-throwing pointcut-ref="userBizExceptionAspect" 
              method="afterThrowing" throwing="e"/>
    </aop:aspect>
</aop:config>

      6、拦截器,发生其他异常时封装成模块自定义异常抛出

package com.xxx.user.facade.exceptions

Class UserBizExceptionFilter implements ThrowAdvice{
   public void afterThrowing(Exception e) throws Throwable{
      //业务异常时直接抛出
      if(e instanceof UserBizException){
           throw (UserBizException) e;
       }
       //dubbo服务异常时封装成子类异常抛出
       else if (e instanceof RpcException){
           throw UserBizException.XXXX;
       }
       //其它系统异常时封装成子类异常抛出
       else{
           throw UserBizException.XXXX;
       }
   }

}

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM