题目
设计支持重试机制的分布式服务异常处理框架
信息
- 类型:问答
- 难度:⭐⭐⭐
考点
自定义异常设计,异常传播控制,重试策略实现,资源清理保证,多线程异常处理
快速回答
实现要点:
- 定义
RetriableException标记可重试异常 - 使用
RetryTemplate封装重试策略(指数退避、熔断) - 通过
try-with-resources确保资源释放 - 结合
ThreadLocal管理上下文状态 - 利用
UncaughtExceptionHandler处理线程池异常
问题场景
在分布式系统中,服务调用常因网络抖动、资源争用等出现瞬时故障。需要设计健壮的异常处理框架,支持:
- 自动重试可恢复异常
- 熔断机制防止雪崩
- 上下文保持与资源清理
- 线程池异常传播
核心实现
1. 异常体系设计
// 标记可重试的异常基类
public class RetriableException extends Exception {
private final Instant failureTime;
public RetriableException(String message, Throwable cause) {
super(message, cause);
this.failureTime = Instant.now();
}
public Instant getFailureTime() { return failureTime; }
}
// 业务异常子类
public class ServiceTimeoutException extends RetriableException {
public ServiceTimeoutException(String serviceName) {
super("Service timeout: " + serviceName, null);
}
}2. 重试模板实现
public class RetryTemplate {
private int maxAttempts = 3;
private long initialBackoff = 1000; // ms
private double multiplier = 1.5;
public <T> T execute(Callable<T> task) throws Exception {
int attempt = 0;
long backoff = initialBackoff;
while (attempt < maxAttempts) {
try (ResourceContext ctx = new ResourceContext()) {
return task.call();
} catch (RetriableException e) {
if (++attempt >= maxAttempts) {
throw new ServiceUnavailableException("Retry exhausted", e);
}
Thread.sleep(backoff);
backoff = (long) (backoff * multiplier);
}
}
throw new IllegalStateException("Unreachable code");
}
}3. 资源安全与上下文管理
// 自动清理资源
class ResourceContext implements AutoCloseable {
private static final ThreadLocal<Map<String, Object>> context = ThreadLocal.withInitial(HashMap::new);
public void put(String key, Object value) {
context.get().put(key, value);
}
@Override
public void close() {
// 清理物理资源(如网络连接)
cleanupConnections();
// 清除ThreadLocal防止内存泄漏
context.remove();
}
}4. 线程池异常处理
ExecutorService executor = Executors.newFixedThreadPool(4, r -> {
Thread t = new Thread(r);
t.setUncaughtExceptionHandler((thread, ex) -> {
if (ex instanceof RetriableException) {
// 提交重试任务到队列
retryQueue.add(new RetryTask(thread.getContextClassLoader(), ex));
} else {
// 记录不可恢复异常
failureLogger.log(thread.getName(), ex);
}
});
return t;
});最佳实践
- 幂等设计:确保重试操作不会导致副作用
- 熔断模式:当失败率超过阈值时停止重试
- 上下文隔离:使用
ThreadLocal需配合try-finally清理 - 异常分类:区分瞬态故障(可重试)和业务错误(不可重试)
常见错误
- 重试死循环:未设置最大尝试次数
- 资源泄漏:未实现
AutoCloseable接口 - 上下文污染:
ThreadLocal未及时清理 - 线程饥饿:重试占用过多线程池资源
扩展知识
- 退避策略:指数退避(Exponential Backoff)添加随机抖动(Jitter)避免惊群效应
- 熔断器模式:参考Netflix Hystrix的三个状态(关闭/打开/半开)
- 响应式重试:Project Reactor的
retryWhen操作符实现异步重试