如何打造一个Dubbo网关--异常处理

在实际业务中,我们通常会自定义一些异常,这些异常不用在业务代码里捕获,通常是全局处理的。全局异常处理不仅能让代码简洁明了、简化开发,更能确保不漏掉异常处理

比如Spring中,通常会使用@ControllerAdvice标注类,在其中处理未在业务代码中直接捕获的异常。@ExceptionHandler(Exception.class)就可以保证异常处理的下限:不给前端非json数据

在分布式系统中,我们也希望能由网关来处理所有的自定义异常

本篇内容主要讲述异常包装和传递,为此需要借助dubbo的consumer-filterprovider-filter,以下代码片段全部来自于plume

dubbo的异常处理

dubbo的非泛化调用异常由ExceptionFilter处理(有GenericService.class != invoker.getInterface()的判断),通过其源码我们可以得知以下几个结论

  1. 受检异常会被直接抛出。因为受检异常类调用方必然存在
  2. 异常类和接口类必须在同一jar包内才会直接抛出。通过ProtectionDomain获取
  3. jdk的异常类和自带的RpcException会直接抛出
  4. 否则会将异常转换成RuntimeException,把原异常调用栈信息直接转为文本

dubbo的泛化调用异常provider由GenericFilter处理;consumer由GenericImplFilter解析,同时是结果出现异常情况可以得知以下几个结论

  1. GenericFilter处理时,是要结果出现异常就把异常包装成GenericException
  2. GenericImplFilter在解析异常时,会尝试还原provider提供的异常类,尝试该异常类的获取和其所有构造
  3. GenericImplFilter只会处理GenericException异常

自定义异常类

对于开发来说主要是定义了PassedException,这个异常用于中断流程业务流程,网关需要处理,并将异常里定义的code和异常信息返回给前端

PassedException在一个独立的包里和XxxResult等实体在一起,每一个client-jar都会引用。从上面可以知道:PassedException会被转换成RuntimeException或者GenericException,而且异常信息还会被修改,这对网关处理很不利

为此,先定义一个ExceptionResult,里面包含对异常的所有需要信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
public class ExceptionResult extends BaseResult {
private boolean failed = true;

// 异常类型
private int type;

// 异常信息
private String message;

// 调用堆栈信息,大部分情况不需要
private String exception;

......
}

然后在PassedException里定义一个toResult方法

1
2
3
public ExceptionResult toResult() {
return new ExceptionResult(PlatformConstants.EXCEPTION_CODE_PASSED, getCode(), getMessage());
}

最后参照dubbo的处理方式,分别定义provider和consumer端的filter,来对异常进行转换和解析

providerFilter

provider比较简单,当结果包含异常时,将异常转换为ExceptionResult返回即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Slf4j
@Activate(group = Constants.PROVIDER, value = "providerFilter", order = 100)
public class ProviderFilterImpl implements Filter {

@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
log.info("[{}] 提供[DUBBO]服务: 方法名: {}, 附加参数: {}, 当前分组: {}", PlatformConstants.APPNAME,
invocation.getMethodName(), invocation.getAttachments(), PlatformConstants.GROUP);
try {
final Result invoke = invoker.invoke(invocation);
if (invoke.hasException()) {
throw invoke.getException();
}
return invoke;
} catch (PassedException ex) {
log.warn("[DUBBO] 业务内部校验不通过: {}", ex.getMessage());
return new RpcResult(ex.toResult());
} catch (RefundException ex) {
log.warn("[DUBBO] 调用了未授权的资源: {}", ex.getMessage());
return new RpcResult(ex.toResult());
} catch (InnerException ex) {
log.warn("[DUBBO] 内部调用异常: {}", ex.getException());
return new RpcResult(ex.toResult());
} catch (Throwable ex) {
log.error("[DUBBO] 调用未知异常: ", ex);
return new RpcResult(new ExceptionResult(PlatformConstants.EXCEPTION_CODE_UNKNOWN,
PlatformExceptionEnum.SYSTEM_BUSY.getCode(), ex.getMessage(), StringUtils.toString(ex)));
}
}
}

consumerFilter

consumer因为要解析,所以会稍微复杂一点。如果在consumer处发现ExceptionResult(泛化调用会给map),则会直接抛出,使得异常在各个系统中传递,直到网关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
@Activate(group = Constants.CONSUMER, value = "consumerFilter", order = -40000)
public class ConsumerFilterImpl implements Filter {

@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
final RpcContext context = RpcContext.getContext();

......

try {
//计算调用时间
final long startTime = System.nanoTime() / 1000;
final Result invoke = invoker.invoke(invocation);
final long endTime = System.nanoTime() / 1000;
log.info("[{}] 消费DUBBO服务[{}]结束, 凭证: {}, 耗时: [{}]μs", PlatformConstants.APPNAME,
invocation.getMethodName(), proof, endTime - startTime);
final Object value = invoke.getValue();
// 泛化调用会返回map,要判断下failed字段
if (value instanceof Map || value instanceof ExceptionResult) {
ExceptionResult exception = null;
if (value instanceof ExceptionResult) {
exception = (ExceptionResult) value;
} else {
final Map map = (Map) value;
if ((boolean) map.getOrDefault("failed", false)) {
exception = new ExceptionResult((int) map.getOrDefault("type", 1),
String.valueOf(map.getOrDefault("code", PlatformExceptionEnum.SYSTEM_BUSY.getCode())),
String.valueOf(map.getOrDefault("message", PlatformExceptionEnum.SYSTEM_BUSY.getMessage())),
String.valueOf(map.getOrDefault("exception", "")));
}
}

if (null != exception) {
// 相当于直接抛出异常
final int type = exception.getType();
if (type == PlatformConstants.EXCEPTION_CODE_PASSED) {
log.warn("[DUBBO] 业务内部校验不通过, 凭证: {}, 原因: {}", proof, exception.getMessage());
return new RpcResult(new PassedException(exception.getCode(), exception.getMessage()));
} else if (type == PlatformConstants.EXCEPTION_CODE_REFUND) {
log.warn("[DUBBO] 调用了未授权的资源, 凭证: {}, 原因: {}", proof, exception.getMessage());
return new RpcResult(new RefundException(exception.getCode(), exception.getMessage()));
} else if (type == PlatformConstants.EXCEPTION_CODE_INNER) {
log.warn("[DUBBO] 内部调用异常, 凭证: {}, 原因: {}", proof, exception.getException());
return new RpcResult(new InnerException(exception.getMessage(), exception.getException()));
} else {
log.error("[DUBBO] 调用未知异常, 凭证: {}, 原因: {}", proof, exception.getException());
return new RpcResult(new InnerException(exception.getMessage(), exception.getException()));
}
}
}
RpcContext.getServerContext().setAttachments(invoke.getAttachments());
return invoke;
} catch (RpcException ex) {
log.warn("[DUBBO] 调用出现已知异常, 凭证: {}, 异常: {}, {}, {}", proof, ex.getCode(), ex.getMessage(), ex.getLocalizedMessage());
if (ex.isSerialization()) {
throw new PassedException(PlatformExceptionEnum.SERIALIZE_ERROR);
} else if (ex.isForbidded()) {
throw new PassedException(PlatformExceptionEnum.FORBIDDEN);
} else if (ex.isNetwork()) {
throw new PassedException(PlatformExceptionEnum.CLIENT_ERROR);
} else if (ex.isTimeout()) {
throw new PassedException(PlatformExceptionEnum.CLIENT_TIMEOUT);
} else {
throw new InnerException(ex.getMessage(), ex.getCause());
}
} catch (GenericException ex) {
log.error("[DUBBO] 调用内部自定义异常, 凭证: {}, 异常: {}", proof, ex);
throw new InnerException(ex.getExceptionMessage(), ex.getCause());
} catch (Exception ex) {
log.error("[DUBBO] 调用未知异常, 凭证: {}, 异常: {}", proof, ex);
throw new InnerException(ex.getMessage(), ex.getCause());
}
}
}

如何打造一个Dubbo网关--异常处理
https://back.pub/post/hh-code-dubbo-gateway-9/
作者
Dash
发布于
2019年3月10日
许可协议