题目
设计分布式服务调用链中的异常处理与资源清理框架
信息
- 类型:问答
- 难度:⭐⭐⭐
考点
自定义异常体系设计,异常传播机制,资源安全释放,分布式上下文保持,异常日志策略
快速回答
在分布式系统中实现安全的异常处理需要:
- 构建分层异常体系区分业务异常和系统异常
- 使用
CompletionException包装异步调用异常 - 通过
try-with-resources和finally块确保资源释放 - 使用
ThreadLocal和MDC传递分布式上下文 - 实现异常转换机制避免敏感信息泄露
- 结合断路器模式(如Resilience4j)处理级联故障
核心挑战与设计原则
分布式系统中的异常处理需解决:跨服务异常传播、资源泄漏风险、上下文丢失、级联故障等问题。设计原则包括:
- 异常分类:业务异常(可恢复) vs 系统异常(不可恢复)
- 资源安全:网络连接、线程池、文件句柄等必须可靠释放
- 上下文完整:跟踪ID、用户信息等跨线程传递
- 异常转换:避免暴露底层实现细节
代码实现示例
// 1. 自定义异常体系设计
public abstract class DistributedException extends Exception {
private final String traceId; // 分布式跟踪ID
public DistributedException(String message, String traceId) {
super(message);
this.traceId = traceId;
}
// 业务异常(可重试)
public static class BusinessException extends DistributedException { /*...*/ }
// 基础设施异常(需熔断)
public static class InfrastructureException extends DistributedException { /*...*/ }
}
// 2. 服务调用层异常处理
public class ServiceInvoker {
private final ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
public CompletableFuture<Response> callRemoteService(Request req) {
return CompletableFuture.supplyAsync(() -> {
try (Connection conn = acquireConnection()) { // try-with-resources自动关闭
// 设置分布式上下文
MDC.put("traceId", req.getTraceId());
Response res = conn.execute(req);
if (res.isError()) {
throw new BusinessException("BUSINESS_ERROR", req.getTraceId());
}
return res;
} catch (SocketTimeoutException e) {
throw new CompletionException(
new InfrastructureException("TIMEOUT", req.getTraceId(), e)
);
}
}, executor).whenComplete((r, e) -> {
// 确保清理ThreadLocal
MDC.clear();
});
}
}
// 3. 全局异常处理器
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(CompletionException.class)
public ResponseEntity<ErrorResponse> handleAsyncException(CompletionException ex) {
Throwable rootCause = ex.getCause();
if (rootCause instanceof DistributedException de) {
// 转换异常为安全响应
return ResponseEntity.status(500)
.body(new ErrorResponse(de.getTraceId(), "SERVICE_UNAVAILABLE"));
}
// 记录未识别异常
log.error("UNKNOWN_ERROR", ex);
return ResponseEntity.internalServerError().build();
}
}最佳实践
- 资源清理:结合
try-with-resources和finally块,确保数据库连接、IO流等物理资源释放 - 上下文传递:使用
ThreadLocal+ MDC(Mapped Diagnostic Context),在异步线程间传递跟踪ID - 异常包装:用
CompletionException包装异步任务异常,保持原始堆栈 - 日志策略:在服务边界记录原始异常,内部只记录日志ID返回客户端
- 熔断机制:集成Resilience4j,在连续异常时触发熔断
常见错误
- 异常吞噬:在
finally块中抛出异常导致原始异常丢失(需嵌套try-catch) - 上下文污染:线程池复用未清理
ThreadLocal引发数据错乱 - 过度包装:多层
catch导致异常堆栈深度膨胀(用e.addSuppressed()替代) - 敏感信息泄露:直接将数据库错误消息返回客户端
- 资源泄漏:未关闭异步操作中的Socket连接
扩展知识
- 响应式编程:Project Reactor的
onErrorResume()/onErrorMap()处理流异常 - 分布式追踪:集成OpenTelemetry实现跨服务异常跟踪
- 故障注入:使用Chaos Monkey测试异常处理鲁棒性
- JVM诊断:通过
-XX:+HeapDumpOnOutOfMemoryError捕获内存异常现场 - 模式应用:Circuit Breaker(熔断器)、Bulkhead(隔离舱)、Retry(重试)组合使用