当前位置: 首页>编程语言>正文

Dubbo封装异常处理Filter报AppResponse represents an concrete business respons dubbo异常是怎么传递的

最近产线上出现了一个下游服务抛出的Error,传递到上游的问题。引发了大家对于Dubbo异常处理的讨论。

笔者翻看了一下Dubbo的源码,针对这个问题,梳理了一下 Dubbo是如何处理业务异常。

先说一下结论:Dubbo实际上不处理异常,只是做异常的传递。下游业务系统如果没有catch住自己内部系统的异常,经由Dubbo调用后,上游系统会收到同样的异常。

那么这一切是如何发生的呢?

下文基于Dubbo 2.6.5版本,从源码角度说明一下Dubbo的异常处理流程。

服务提供端捕获异常

服务提供端接收到消费端的调用请求后,一般情况下会经由以下调用链路

  • ChannelEventRunnable.run
  • DecodeHandler.received
  • HeaderExchangeHandler.received
  • HeaderExchangeHandler.handleRequest

这几步主要是解析上游请求报文。随后会进行Dubbo协议的处理

  • DubboProtocol.reply
@Override
public Object reply(ExchangeChannel channel, Object message) throws RemotingException {
    if (message instanceof Invocation) {
        Invocation inv = (Invocation) message;
        Invoker<?> invoker = getInvoker(channel, inv);
        // need to consider backward-compatibility if it's a callback
        if (Boolean.TRUE.toString().equals(inv.getAttachments().get(IS_CALLBACK_SERVICE_INVOKE))) {
            String methodsStr = invoker.getUrl().getParameters().get("methods");
            boolean hasMethod = false;
            if (methodsStr == null || methodsStr.indexOf(",") == -1) {
                hasMethod = inv.getMethodName().equals(methodsStr);
            } else {
                String[] methods = methodsStr.split(",");
                for (String method : methods) {
                    if (inv.getMethodName().equals(method)) {
                        hasMethod = true;
                        break;
                    }
                }
            }
            if (!hasMethod) {
                logger.warn(new IllegalStateException("The methodName " + inv.getMethodName()
                                                      + " not found in callback service interface ,invoke will be ignored."
                                                      + " please update the api interface. url is:"
                                                      + invoker.getUrl()) + " ,invocation is :" + inv);
                return null;
            }
        }
        RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());
        return invoker.invoke(inv);
    }
    throw new RemotingException(channel, "Unsupported request: "
                                + (message == null ? null : (message.getClass().getName() + ": " + message))
                                + ", channel: consumer: " + channel.getRemoteAddress() + " --> provider: " + channel.getLocalAddress());
}

这里的reply是匿名内部类的方法。其关键是invoker.invoke(inv);这一句调用,此调用会触发一系列的filter链和listener链的调用,如 EchoFilter、ClassLoaderFilter、GenericFilter…ExceptionFilter。

其中最关键的,就是ExceptionFilter了。

@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
    try {
        Result result = invoker.invoke(invocation);
        // $-- 不会处理GenericService类型invoker的异常
        if (result.hasException() && GenericService.class != invoker.getInterface()) {
            try {
                Throwable exception = result.getException();

                // directly throw if it's checked exception
                // $-- check Exception直接抛出
                if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) {
                    return result;
                }
                // directly throw if the exception appears in the signature
                // $-- 如果异常已经在被调用方法中声明了,那么也直接抛出
                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;
                }

                // for the exception not found in method's signature, print ERROR message in server's log.
                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);

                // directly throw if exception class and interface class are in the same jar file.
                // $-- 如果异常类和接口类在同一个jar包中,那么也直接抛出异常
                String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
                String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
                if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) {
                    return result;
                }
                // directly throw if it's JDK exception
                // $-- JDK异常也直接抛出
                String className = exception.getClass().getName();
                if (className.startsWith("java.") || className.startsWith("javax.")) {
                    return result;
                }
                // directly throw if it's dubbo exception
                // $-- Dubbo异常也直接抛出
                if (exception instanceof RpcException) {
                    return result;
                }

                // otherwise, wrap with RuntimeException and throw back to the client
                // $-- 其他类型的异常(一般是消费者端不存在的自定义异常),将异常转换为字符串,并包装成一个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会处理一些特殊的异常,其主要目的是为了避免下游系统抛出的自定义异常,上游系统没有,从而导致上游出现反序列化问题

你可能也注意到了,ExceptionFilter中,调用 invoker.invoke 方法的返回结果是一个Result类型的变量,而这个Result类型的变量实际上已经捕获了相应的异常,并封装到了其对象内部。

那么此处的Result对象是如何构造的呢?让我们继续往下看调用链路。

  • InvokerWrapper.invoke
  • DelegateProviderMetaDataInvoker.invoke
  • AbstractProxyInvoker.invoke
@Override
public Result invoke(Invocation invocation) throws RpcException {
    try {
        return new RpcResult(doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments()));
    } catch (InvocationTargetException e) {
        return new RpcResult(e.getTargetException());
    } catch (Throwable e) {
        throw new RpcException("Failed to invoke remote proxy method " + invocation.getMethodName() + " to " + getUrl() + ", cause: " + e.getMessage(), e);
    }
}

在AbstractProxyInvoker.invoke方法中,我们看到,Result对象实际上是在这里产生的。

doInvoke方法应该会调用相应的实现方法,如果出现异常了,那么应该就会被这里的 Throwable 捕获到。因此Dubbo不会抛异常。

但问题真的是这样么?

我们注意到,其实doInvoke方法还会依次调用如下方法:

  • JavassistProxyFactory.doInvoke
  • Wrapper1.invokeMethod(Wrapper1.java)
  • UserServiceImpl.getNameById(真正的目标方法)

可以看到,中间通过了JavassistProxyFactory调用了一层。我们知道Javassist会帮助Dubbo生成一些代码。那么会不会在这生成的代码中,又做了一些骚操作呢?

在JavassistProxyFactory的getInvoker方法中,我们可以看到:

@Override
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
    // TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
    final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
    return new AbstractProxyInvoker<T>(proxy, type, url) {
        @Override
        protected Object doInvoke(T proxy, String methodName,
                                  Class<?>[] parameterTypes,
                                  Object[] arguments) throws Throwable {
            return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
        }
    };
}

doInvoke方法,实际上是由wrapper来执行的。而这个wrapper呢,是通过Wrapper.getWrapper来动态生成的代码。因此在实际调用运行时进行debug的话,你是看不到这代码执行。

public static Wrapper getWrapper(Class<?> c) {
    while (ClassGenerator.isDynamicClass(c)) // can not wrapper on dynamic class.
        c = c.getSuperclass();

    if (c == Object.class)
        return OBJECT_WRAPPER;

    Wrapper ret = WRAPPER_MAP.get(c);
    if (ret == null) {
        ret = makeWrapper(c);
        WRAPPER_MAP.put(c, ret);
    }
    return ret;
}

上述代码中的 makeWrapper方法,就是动态生成代码的代码。(太长了,这里就不展示了)

如果在下游系统启动的时候,在此处相应位置加上debug,就可以看到相应的动态生成的代码了。这里我截取了一下的 invokeMethod 动态生成的实际方法。

public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws java.lang.reflect.InvocationTargetException {
    cn.hewie.hservice.facade.impl.UserServiceImpl w;
    try {
        w = ((cn.hewie.hservice.facade.impl.UserServiceImpl) );
    } catch (Throwable e) {
        throw new IllegalArgumentException(e);
    }
    try {
        if ("getNameById".equals() && .length == 1) {
            return ($w) w.getNameById((java.lang.String) [0]);
        }
    } catch (Throwable e) {
        throw new java.lang.reflect.InvocationTargetException(e);
    }
    throw new com.alibaba.dubbo.common.bytecode.NoSuchMethodException("Not found method \"" +  + "\" in class cn.hewie.hservice.facade.impl.UserServiceImpl.");
}

哈,在这里我们发现,通过try和catch方法,将我们实际调用的getNameById方法包了一层。如果getNameById方法出现异常的话,会被包装成InvocationTargetException。

如果此处抛出了异常,那么在AbstractProxyInvoker的invoke方法处,会捕获异常,然后封装成相应的RpcResult。

@Override
public Result invoke(Invocation invocation) throws RpcException {
    try {
        return new RpcResult(doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments()));
    } catch (InvocationTargetException e) {
        return new RpcResult(e.getTargetException());
    } catch (Throwable e) {
        throw new RpcException("Failed to invoke remote proxy method " + invocation.getMethodName() + " to " + getUrl() + ", cause: " + e.getMessage(), e);
    }
}

再往上,ExceptionFilter拿到结果了之后,会判断是哪一种类型的异常。如果是Jdk的Error,如:NoClassDefError,最终会被以下代码块捕获,从而打印出日志中的那段"Got unchecked and undeclared…"

// for the exception not found in method's signature, print ERROR message in server's log.
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);

// directly throw if exception class and interface class are in the same jar file.
// $-- 如果异常类和接口类在同一个jar包中,那么也直接抛出异常
String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) {
    return result;
}
// directly throw if it's JDK exception
// $-- JDK异常也直接抛出
String className = exception.getClass().getName();
if (className.startsWith("java.") || className.startsWith("javax.")) {
    return result;
}

上述介绍了服务提供端的异常捕获流程。总结下来就是,Dubbo将业务异常包装在RpcResult中,然后返回给了上游。

那么上游消费端是如何解析这个异常的呢?

消费端重放异常

当服务提供端抛出了一个业务异常时,Dubbo并不会报错,而是将这个异常包裹到一个 RpcResult 对象中了。

消费端拿到这个 RpcResult 之后,按照调用的链路顺序,一层一层向上剥开处理。(这个过程可以参考笔者的另一篇博文:Dubbo服务调用的过程)

最终处理异常,是在 InvokerInvocationHandler 这个类中。

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    Class<?>[] parameterTypes = method.getParameterTypes();
    if (method.getDeclaringClass() == Object.class) {
        return method.invoke(invoker, args);
    }
    if ("toString".equals(methodName) && parameterTypes.length == 0) {
        return invoker.toString();
    }
    if ("hashCode".equals(methodName) && parameterTypes.length == 0) {
        return invoker.hashCode();
    }
    if ("equals".equals(methodName) && parameterTypes.length == 1) {
        return invoker.equals(args[0]);
    }
    return invoker.invoke(new RpcInvocation(method, args)).recreate();
}

InvokerInvocationHandler 这个类的 invoke 方法,调用得到结果之后,会调用 recreate() 这个方法。

就是这个不起眼的方法,进行了异常的处理。它的逻辑如下:

@Override
public Object recreate() throws Throwable {
    if (exception != null) {
        throw exception;
    }
    return result;
}

如果 RpcResult 中存在异常,就直接将异常抛出。这样消费端就得到了和业务端一样的业务异常了。

关于应用域的异常处理

前文笔者提到,产线出现了下游异常抛出到上游的问题。其实在业务开发中,我们的代码中,在对外提供的facade服务层,一般都进行了 try … catch的异常处理。

之所以会出现这个问题,是因为下游服务抛出的是一个 Error,而我们的上下游应用代码中捕获的都是 Exception。因此没有兜住,导致一个Error报错,将上游也干趴了。从而导致产线数据和业务问题。

真正的业务应用中,上游对下游的调用结果,可能并没有“那么关心”。也许下游的某个服务挂了/执行出现了异常,对上游也没有很大的影响。此时为了保证业务的正常,应当采取相应的降级措施。

针对这一次的问题,笔者建议对于这种“不影响主流程”的下游服务调用,可以catch一个Throwable异常。这样才能做到真正的降级!

异常日志

[DubboServerHandler-192.168.10.213:20886-thread-3] ERROR - com.alibaba.dubbo.rpc.filter.ExceptionFilter.invoke(ExceptionFilter.java:85) -  [DUBBO] Got unchecked and undeclared exception which called by 192.168.10.213. service: cn.hewie.hservice.facade.UserService, method: getNameById, exception: java.lang.NoClassDefFoundError: Could not initialize class cn.hewie.hservice.facade.MockNoClassDefFoundError, dubbo version: 2.6.5, current host: 192.168.10.213
java.lang.NoClassDefFoundError: Could not initialize class cn.hewie.hservice.facade.MockNoClassDefFoundError
	at cn.hewie.hservice.facade.impl.UserServiceImpl.getNameById(UserServiceImpl.java:20) ~[classes/:?]
	at com.alibaba.dubbo.common.bytecode.Wrapper1.invokeMethod(Wrapper1.java) ~[dubbo-2.6.5.jar:2.6.5]
	at com.alibaba.dubbo.rpc.proxy.javassist.JavassistProxyFactory.doInvoke(JavassistProxyFactory.java:47) ~[dubbo-2.6.5.jar:2.6.5]
	at com.alibaba.dubbo.rpc.proxy.AbstractProxyInvoker.invoke(AbstractProxyInvoker.java:76) ~[dubbo-2.6.5.jar:2.6.5]
	at com.alibaba.dubbo.config.invoker.DelegateProviderMetaDataInvoker.invoke(DelegateProviderMetaDataInvoker.java:52) ~[dubbo-2.6.5.jar:2.6.5]
	at com.alibaba.dubbo.rpc.protocol.InvokerWrapper.invoke(InvokerWrapper.java:56) ~[dubbo-2.6.5.jar:2.6.5]
	at com.alibaba.dubbo.rpc.filter.ExceptionFilter.invoke(ExceptionFilter.java:62) [dubbo-2.6.5.jar:2.6.5]
	at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper.invoke(ProtocolFilterWrapper.java:72) [dubbo-2.6.5.jar:2.6.5]
	at com.alibaba.dubbo.monitor.support.MonitorFilter.invoke(MonitorFilter.java:75) [dubbo-2.6.5.jar:2.6.5]
	at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper.invoke(ProtocolFilterWrapper.java:72) [dubbo-2.6.5.jar:2.6.5]
	at com.alibaba.dubbo.rpc.filter.TimeoutFilter.invoke(TimeoutFilter.java:42) [dubbo-2.6.5.jar:2.6.5]
	at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper.invoke(ProtocolFilterWrapper.java:72) [dubbo-2.6.5.jar:2.6.5]
	at com.alibaba.dubbo.rpc.protocol.dubbo.filter.TraceFilter.invoke(TraceFilter.java:78) [dubbo-2.6.5.jar:2.6.5]
	at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper.invoke(ProtocolFilterWrapper.java:72) [dubbo-2.6.5.jar:2.6.5]
	at com.alibaba.dubbo.rpc.filter.ContextFilter.invoke(ContextFilter.java:73) [dubbo-2.6.5.jar:2.6.5]
	at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper.invoke(ProtocolFilterWrapper.java:72) [dubbo-2.6.5.jar:2.6.5]
	at com.alibaba.dubbo.rpc.filter.GenericFilter.invoke(GenericFilter.java:138) [dubbo-2.6.5.jar:2.6.5]
	at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper.invoke(ProtocolFilterWrapper.java:72) [dubbo-2.6.5.jar:2.6.5]
	at com.alibaba.dubbo.rpc.filter.ClassLoaderFilter.invoke(ClassLoaderFilter.java:38) [dubbo-2.6.5.jar:2.6.5]
	at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper.invoke(ProtocolFilterWrapper.java:72) [dubbo-2.6.5.jar:2.6.5]
	at com.alibaba.dubbo.rpc.filter.EchoFilter.invoke(EchoFilter.java:38) [dubbo-2.6.5.jar:2.6.5]
	at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper.invoke(ProtocolFilterWrapper.java:72) [dubbo-2.6.5.jar:2.6.5]
	at com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol.reply(DubboProtocol.java:104) [dubbo-2.6.5.jar:2.6.5]
	at com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.handleRequest(HeaderExchangeHandler.java:96) [dubbo-2.6.5.jar:2.6.5]
	at com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.received(HeaderExchangeHandler.java:173) [dubbo-2.6.5.jar:2.6.5]
	at com.alibaba.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:51) [dubbo-2.6.5.jar:2.6.5]
	at com.alibaba.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:57) [dubbo-2.6.5.jar:2.6.5]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_171]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_171]
	at java.lang.Thread.run(Thread.java:748) [?:1.8.0_171]



https://www.xamrdz.com/lan/5mc1932609.html

相关文章: