zoukankan      html  css  js  c++  java
  • dubbo异常处理

    dubbo异常处理

    我们的项目使用了dubbo进行不同系统之间的调用。
    每个项目都有一个全局的异常处理,对于业务异常,我们会抛出自定义的业务异常(继承RuntimeException)。
    全局的异常处理会根据不同的异常类型进行不同的处理。
    最近我们发现,某个系统调用dubbo请求,provider端(服务提供方)抛出了自定义的业务异常,但consumer端(服务消费方)拿到的并不是自定义的业务异常。

    这是为什么呢?还需要从dubbo的ExceptionFilter说起

    我们来看看dubbo官方文档的推荐处理方式是什么
    建议使用异常汇报错误,而不是返回错误码,异常信息能携带更多信息,并且语义更友好。
    
    如果担心性能问题,在必要时,可以通过 override 掉异常类的 fillInStackTrace() 方法为空方法,使其不拷贝栈信息。
    
    查询方法不建议抛出 checked 异常,否则调用方在查询时将过多的 try...catch,并且不能进行有效处理。
    
    服务提供方不应将 DAO 或 SQL 等异常抛给消费方,应在服务实现中对消费方不关心的异常进行包装,否则可能出现消费方无法反序列化相应异常。
    
    dubbo在代码中的处理方式是什么?
    /*
     * 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;
            }
        }
     
    }
    
    

    从上面我们可以看出,dubbo的处理方式主要是:

    1. 如果provider实现了GenericService接口,直接抛出
    2. 如果是checked异常,直接抛出
    3. 在方法签名上有声明,直接抛出
    4. 异常类和接口类在同一jar包里,直接抛出
    5. 是JDK自带的异常,直接抛出
    6. 是Dubbo本身的异常,直接抛出
    7. 否则,包装成RuntimeException抛给客户端
    接下来我们来测试一下

    我们自定义一个SelfException

    public class SelfException extends RuntimeException {
            public SelfException() {
            }
    
            public SelfException(String message, Throwable cause) {
                super(message, cause);
            }
    
            public SelfException(String message) {
                super(message);
            }
    
            public SelfException(Throwable cause) {
                super(cause);
            }
    }
    

    在服务端中抛出一个自定义的异常

    public class DemoServiceImpl implements DemoService {
    
        @Override
        public String sayHello(String name) {
            System.out.println("[" + new SimpleDateFormat("HH:mm:ss").format(new Date()) + "] Hello " + name + ", request from consumer: " + RpcContext.getContext().getRemoteAddress());
            try {
                JSONObject.parse("aaa");
            }catch (Exception e){
                throw new SelfException(e);
            }
            return "Hello " + name + ", response from provider: " + RpcContext.getContext().getLocalAddress();
        }
        
    }
    
    

    启动服务对并对服务端进行访问,我们可以发现

    服务端的日志

    Caused by: com.alibaba.fastjson.JSONException: syntax error, pos 1, json : aaa
    	at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1436)
    	at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1322)
    	at com.alibaba.fastjson.JSON.parse(JSON.java:152)
    	at com.alibaba.fastjson.JSON.parse(JSON.java:162)
    	at com.alibaba.fastjson.JSON.parse(JSON.java:131)
    	at org.apache.dubbo.demo.provider.DemoServiceImpl.sayHello(DemoServiceImpl.java:34)
    	... 29 more
    

    客户端的日志

    java.lang.RuntimeException: org.apache.dubbo.demo.provider.SelfException: com.alibaba.fastjson.JSONException: syntax error, pos 1, json : aaa
    org.apache.dubbo.demo.provider.SelfException: com.alibaba.fastjson.JSONException: syntax error, pos 1, json : aaa
    	at org.apache.dubbo.demo.provider.DemoServiceImpl.sayHello(DemoServiceImpl.java:36)
    	at org.apache.dubbo.common.bytecode.Wrapper1.invokeMethod(Wrapper1.java)
    	at org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory$1.doInvoke(JavassistProxyFactory.java:47)
    	at org.apache.dubbo.rpc.proxy.AbstractProxyInvoker.invoke(AbstractProxyInvoker.java:85)
    	at org.apache.dubbo.config.invoker.DelegateProviderMetaDataInvoker.invoke(DelegateProviderMetaDataInvoker.java:52)
    	at org.apache.dubbo.rpc.protocol.InvokerWrapper.invoke(InvokerWrapper.java:56)
    	at org.apache.dubbo.rpc.filter.ExceptionFilter.invoke(ExceptionFilter.java:57)
    	at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:72)
    	at org.apache.dubbo.monitor.support.MonitorFilter.invoke(MonitorFilter.java:75)
    	at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:72)
    	at org.apache.dubbo.rpc.filter.TimeoutFilter.invoke(TimeoutFilter.java:42)
    	at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:72)
    	at org.apache.dubbo.rpc.protocol.dubbo.filter.TraceFilter.invoke(TraceFilter.java:79)
    	at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:72)
    	at org.apache.dubbo.rpc.filter.ContextFilter.invoke(ContextFilter.java:77)
    	at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:72)
    	at org.apache.dubbo.rpc.filter.GenericFilter.invoke(GenericFilter.java:138)
    	at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:72)
    	at org.apache.dubbo.rpc.filter.ClassLoaderFilter.invoke(ClassLoaderFilter.java:38)
    	at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:72)
    	at org.apache.dubbo.rpc.filter.EchoFilter.invoke(EchoFilter.java:39)
    	at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:72)
    	at org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol$1.reply(DubboProtocol.java:114)
    	at org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.handleRequest(HeaderExchangeHandler.java:103)
    	at org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.received(HeaderExchangeHandler.java:200)
    	at org.apache.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:51)
    	at org.apache.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:57)
    	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    	at java.lang.Thread.run(Thread.java:745)
    

    可以看出, 我们这里并没有抛出自定义的异常, 从而会导致客户端没有捕捉到自定义的异常.

    如何正确捕获业务异常

    抛出一个自定义异常有这么麻烦吗? 主要原因是dubbo没有支持的原因.

    既然这样,我们把dubbo变的支持不就可以了?

    是的.把源码改一下就OK了.如下:

    String className = exception.getClass().getName();
    if (className.startsWith("java.") || className.startsWith("javax.")) {
    	return result;
    }
    // directly throw if it's dubbo exception
    if (exception instanceof RpcException) {
    	return result;
    }
    //在这里添加我自己定义的异常类
    if (exception instanceof  SelfException) {
    return result;
    }
    

    或者直接将117行的RuntimeException替换成自己的自定义异常!这样就从根本上解决了异常处理的问题.后续有其他问题,也可以直接修改.

  • 相关阅读:
    PHP中curl_init和file_get_contents配合使用
    在PHP语言中使用JSON
    网页中获取IFrame内容
    Golden Gate
    windows 7某些中文程序乱码
    Oracle的同义词(synonyms)总结
    VERITAS容灾技术方案
    VERITAS 备份及容灾方案建议书
    1 FAST ESP 简介
    Linux文件查找命令find,xargs详述
  • 原文地址:https://www.cnblogs.com/luozhiyun/p/10251585.html
Copyright © 2011-2022 走看看