浅谈dubbo的ExceptionFilter异常处理
背景
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;
- }
- }
- }
代码分析
逻辑0
- if (result.hasException() && GenericService.class != invoker.getInterface()) {
- //...
- }
- return result;
调用结果有异常且未实现GenericService接口,进入后续判断逻辑,否则直接返回结果。
- /**
- * 通用服务接口
- *
- * @author william.liangf
- * @export
- */
- public interface GenericService {
- /**
- * 泛化调用
- *
- * @param method 方法名,如:findPerson,如果有重载方法,需带上参数列表,如:findPerson(java.lang.String)
- * @param parameterTypes 参数类型
- * @param args 参数列表
- * @return 返回值
- * @throws Throwable 方法抛出的异常
- */
- Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException;
- }
泛接口实现方式主要用于服务器端没有API接口及模型类元的情况,参数及返回值中的所有POJO均用Map表示,通常用于框架集成,比如:实现一个通用的远程服务Mock框架,可通过实现GenericService接口处理所有服务请求。
不适用于此场景,不在此处探讨。
逻辑1
- // 如果是checked异常,直接抛出
- if (! (exception instanceof RuntimeException) && (exception instanceof Exception)) {
- return result;
- }
不是RuntimeException类型的异常,并且是受检异常(继承Exception),直接抛出。
provider端想抛出受检异常,必须在api上明确写明抛出受检异常;consumer端如果要处理受检异常,也必须使用明确写明抛出受检异常的api。
provider端api新增 自定义的 受检异常, 所有的 consumer端api都必须升级,同时修改代码,否则无法处理这个特定异常。
- try {
- decode(channel, inputStream);
- } catch (Throwable e) {
- if (log.isWarnEnabled()) {
- log.warn("Decode rpc result failed: " + e.getMessage(), e);
- }
- response.setStatus(Response.CLIENT_ERROR);
- response.setErrorMessage(StringUtils.toString(e));
- } finally {
- hasDecoded = true;
- }
DefaultFuture判断请求返回的结果,最后抛出RemotingException:
- private Object returnFromResponse() throws RemotingException {
- Response res = response;
- if (res == null) {
- throw new IllegalStateException("response cannot be null");
- }
- if (res.getStatus() == Response.OK) {
- return res.getResult();
- }
- if (res.getStatus() == Response.CLIENT_TIMEOUT || res.getStatus() == Response.SERVER_TIMEOUT) {
- throw new TimeoutException(res.getStatus() == Response.SERVER_TIMEOUT, channel, res.getErrorMessage());
- }
- throw new RemotingException(channel, res.getErrorMessage());
- }
DubboInvoker捕获RemotingException,抛出RpcException:
- try {
- boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);
- boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
- int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY,Constants.DEFAULT_TIMEOUT);
- if (isOneway) {
- boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
- currentClient.send(inv, isSent);
- RpcContext.getContext().setFuture(null);
- return new RpcResult();
- } else if (isAsync) {
- ResponseFuture future = currentClient.request(inv, timeout) ;
- RpcContext.getContext().setFuture(new FutureAdapter<Object>(future));
- return new RpcResult();
- } else {
- RpcContext.getContext().setFuture(null);
- return (Result) currentClient.request(inv, timeout).get();
- }
- } catch (TimeoutException e) {
- throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
- } catch (RemotingException e) {
- throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
- }
调用栈:
FailOverClusterInvoker.doInvoke -...-> DubboInvoker.doInvoke -> ReferenceCountExchangeClient.request -> HeaderExchangeClient.request -> HeaderExchangeChannel.request -> AbstractPeer.send -> NettyChannel.send -> AbstractChannel.write -> Channels.write --back_to--> DubboInvoker.doInvoke -> DefaultFuture.get -> DefaultFuture.returnFromResponse -> throw new RemotingException
- com.alibaba.dubbo.rpc.RpcException: Failed to invoke the method triggerCheckedException in the service com.xxx.api.DemoService. Tried 1 times of the providers [192.168.1.101:20880] (1/1) from the registry 127.0.0.1:2181 on the consumer 192.168.1.101 using the dubbo version 3.1.9. Last error is: Failed to invoke remote method: triggerCheckedException, provider: dubbo://192.168.1.101:20880/com.xxx.api.DemoService?xxx, cause: java.io.IOException: Response data error, expect Throwable, but get {cause=(this Map), detailMessage=null, suppressedExceptions=[], stackTrace=[Ljava.lang.StackTraceElement;@23b84919}
- java.io.IOException: Response data error, expect Throwable, but get {cause=(this Map), detailMessage=null, suppressedExceptions=[], stackTrace=[Ljava.lang.StackTraceElement;@23b84919}
- at com.alibaba.dubbo.rpc.protocol.dubbo.DecodeableRpcResult.decode(DecodeableRpcResult.java:94)
逻辑2
- // 在方法签名上有声明,直接抛出
- 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;
- }
如果在provider端的api明确写明抛出运行时异常,则会直接被抛出。
答案是和上面一样,抛出RpcException。
因此如果consumer端不care这种异常,则不需要任何处理;
consumer端有这种异常(路径要完全一致,包名+类名),则不需要任何处理;
没有这种异常,又想进行处理,则需要引入这个异常进行处理(方法有多种,比如升级api,或引入/升级异常所在的包)。
逻辑3
- // 异常类和接口类在同一jar包里,直接抛出
- String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
- String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
- if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)){
- return result;
- }
如果异常类和接口类在同一个jar包中,直接抛出。
逻辑4
- // 是JDK自带的异常,直接抛出
- String className = exception.getClass().getName();
- if (className.startsWith("java.") || className.startsWith("javax.")) {
- return result;
- }
以java.或javax.开头的异常直接抛出。
逻辑5
- // 是Dubbo本身的异常,直接抛出
- if (exception instanceof RpcException) {
- return result;
- }
dubbo自身的异常,直接抛出。
逻辑6
- // 否则,包装成RuntimeException抛给客户端
- return new RpcResult(new RuntimeException(StringUtils.toString(exception)));
不满足上述条件,会做toString处理并被封装成RuntimeException抛出。
核心思想
如何正确捕获业务异常
了解了ExceptionFilter,解决上面提到的问题就很简单了。
浅谈dubbo的ExceptionFilter异常处理的更多相关文章
- 浅谈dubbo服务
Dubbo分布式服 推荐大家一个画图工具:https://www.processon.com/i/572d51efe4b0c3c74981ec14 1.Dubbo是一个分布式服务框架,致力于提供高性能 ...
- 浅谈微服务架构与服务治理的Eureka和Dubbo
前言 本来计划周五+周末三天自驾游,谁知人算不如天算,周六恰逢台风来袭,湖州附近的景点全部关停,不得已只能周五玩完之后,于周六踩着台风的边缘逃回上海.周末过得如此艰难,这次就聊点务虚的话题,一是浅谈微 ...
- 浅谈Java的throw与throws
转载:http://blog.csdn.net/luoweifu/article/details/10721543 我进行了一些加工,不是本人原创但比原博主要更完善~ 浅谈Java异常 以前虽然知道一 ...
- 浅谈WebService的版本兼容性设计
在现在大型的项目或者软件开发中,一般都会有很多种终端, PC端比如Winform.WebForm,移动端,比如各种Native客户端(iOS, Android, WP),Html5等,我们要满足以上所 ...
- 浅谈Hybrid技术的设计与实现
前言 浅谈Hybrid技术的设计与实现 浅谈Hybrid技术的设计与实现第二弹 浅谈Hybrid技术的设计与实现第三弹——落地篇 随着移动浪潮的兴起,各种APP层出不穷,极速的业务扩展提升了团队对开发 ...
- jsp内置对象浅谈
jsp内置对象浅谈 | 浏览:1184 | 更新:2013-12-11 16:01 JSP内置对象:我们在使用JSP进行页面编程时可以直接使用而不需自己创建的一些Web容器已为用户创建好的JSP内置对 ...
- 【架构】浅谈web网站架构演变过程
浅谈web网站架构演变过程 前言 我们以javaweb为例,来搭建一个简单的电商系统,看看这个系统可以如何一步步演变. 该系统具备的功能: 用户模块:用户注册和管理 商品模块:商品展示和管 ...
- [C#]6.0新特性浅谈
原文:[C#]6.0新特性浅谈 C#6.0出来也有很长一段时间了,虽然新的特性和语法趋于稳定,但是对于大多数程序猿来说,想在工作中用上C#6.0估计还得等上不短的一段时间.所以现在再来聊一聊新版本带来 ...
- Android安全开发之启动私有组件漏洞浅谈
0x00 私有组件浅谈 android应用中,如果某个组件对外导出,那么这个组件就是一个攻击面.很有可能就存在很多问题,因为攻击者可以以各种方式对该组件进行测试攻击.但是开发者不一定所有的安全问题都能 ...
随机推荐
- Hadoop windows 环境配置
下载 winutils 点击 这里下载 winutils 包,并解压缩. 此处解压缩后目录位置为 D:\software\hadoop2.6 配置环境变量 变量名 变量值 HADOOP_USER_NA ...
- 【15】模板方法模式(Template Method)
一.引言 提到模板,大家肯定不免想到生活中的“简历模板”.“论文模板”.“Word中模版文件”等.在现实生活中,模板的概念就是——有一个规定的格式,然后每个人都可以根据自己的需求或情况去更新它.例如简 ...
- maven插件 按配置加载不同环境配置文件进行打包(maven-war-plugin)
1.配置多种不同环境 如(本地local,开发dev,测试test 环境等) <profiles> <profile> <id>local</id> & ...
- BZOJ2388: 旅行规划(分块 凸包)
题意 题目链接 Sol 直接挂队爷的题解了 分块题好难调啊qwq #include<bits/stdc++.h> #define LL long long using namespace ...
- Excel indirect引用其它xlsx文件内容作为下拉框
效果如下图: 在第一个excel文件中有一个下拉框 这里面的选项,需要从另外一个Excel文件中读取内容,另外一个Excel文件如下: 实现的步骤如下: 1.新建一个Excel文件select.xls ...
- Python 基于python实现的http接口自动化测试框架(含源码)
基于python实现的http+json协议接口自动化测试框架(含源码) by:授客 QQ:1033553122 欢迎加入软件性能测试交流 QQ群:7156436 由于篇幅问题,采用百度网 ...
- nmon 及nmon analyser工具使用简介
nmon及nmon analyser工具使用简介 by:授客 QQ:1033553122 下载地址 http://nmon.sourceforge.net/pmwiki.php?n=Site.Down ...
- jquery中ajax的dataType的各种属性含义
参考ajax api文档:http://www.w3school.com.cn/jquery/ajax_ajax.asp dateType后接受的参数参数类型:string 预期服务器返回的数据类型. ...
- 使用 HPC Pack 为 Azure 中的 Windows HPC 工作负荷创建和管理群集的选项
利用 Microsoft HPC Pack 和 Azure 的计算与基础结构服务,创建和管理基于云的高性能计算 (HPC) 群集. HPC Pack 是在 Azure 和 Windows Server ...
- 服务器重启可能会导致SQL Server中部分数据库变为single user mode
今天检查公司生产服务器的SQL Server数据库,惊讶的发现有三个生产数据库变为了single user mode.奇怪的是没有任何人和程序执行过SQL语句将这三个数据库设置为single user ...