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替換成自己的自定義異常!這樣就從根本上解決了異常處理的問題.后續有其他問題,也可以直接修改.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM