一、为什么需要重试机制?
在实际开发中,网络请求、数据库操作、远程服务调用等场景都可能因瞬时故障 (如网络抖动、服务短暂不可用)导致失败。重试机制的核心思想是:通过有限次数的重复尝试,提高操作最终成功的概率。这就像打电话时对方占线,我们会选择稍后再拨,而不是直接放弃。
二、代码案例中的关键语法现象
让我们先观察代码片段:
java
return Retryer.<SendResult>newInstance()
.maxAttempt(maxAttempt)
.callable(new Callable<SendResult>() {
@Override
public SendResult call() throws Exception {
return doSend(messageId, mqMessage);
}
}).retryCall();
1. 泛型(Generics)
java
Retryer.<SendResult>newInstance()
- 作用 :通过
<SendResult>
指定泛型类型,声明Retryer
最终返回的结果类型为SendResult
。 - 意义:泛型保证了链式调用中所有方法的类型一致性,编译器会检查每一步操作的类型是否正确,避免运行时类型转换错误。
2. 匿名内部类
java
new Callable<SendResult>() {
@Override
public SendResult call() throws Exception {
return doSend(messageId, mqMessage);
}
}
- 本质 :
Callable
是一个函数式接口,定义了一个待执行的任务(类似Runnable
,但可以返回结果和抛出异常)。 - 匿名内部类的意义 :将需要重试的核心逻辑
doSend()
封装成一个独立的可执行对象,实现了逻辑与重试策略的解耦。
3. 链式调用(Fluent API)
java
Retryer.newInstance().maxAttempt(...).callable(...).retryCall()
- 链式调用的本质 :每个方法返回
this
对象,允许连续调用多个方法。 - 优势 :
- 代码可读性强,符合自然语言描述(例如"创建 Retryer 实例→设置最大重试次数→设置任务→执行")。
- 避免冗长的临时变量,代码更加紧凑。
三、代码中隐含的设计模式
1. 构建器模式(Builder Pattern)
java
Retryer.newInstance().maxAttempt(3).callable(...)
- 模式解析 :
Retryer
类的设计者通过静态方法newInstance()
创建一个"空"的构建器。- 通过链式方法(如
maxAttempt()
、callable()
)逐步填充配置参数。 - 最终通过
retryCall()
完成对象的构建和执行。
- 优势 :
- 避免构造方法参数爆炸(例如
new Retryer(3, callable, ...)
参数过多)。 - 允许灵活组合配置项,例如未来扩展时可以添加
retryInterval()
方法设置重试间隔。
- 避免构造方法参数爆炸(例如
2. 命令模式(Command Pattern)
java
Callable<SendResult> task = new Callable<>() {
public SendResult call() { ... }
};
- 模式解析 :
- 将具体的业务操作
doSend()
封装成一个Callable
对象。 Retryer
不需要关心具体操作是什么,只需要负责执行这个命令对象,并处理重试逻辑。
- 将具体的业务操作
- 意义 :实现了**操作请求者(Retryer)和操作执行者(doSend)**的解耦。
3. 模板方法模式(Template Method Pattern)
java
// 伪代码:Retryer 内部实现
public class Retryer<T> {
public T retryCall() {
for (int i=0; i<maxAttempt; i++) {
try {
return callable.call();
} catch (Exception e) {
if (i == maxAttempt-1) throw e;
}
}
throw new RetryException();
}
}
- 模式解析 :
retryCall()
定义了重试的固定流程(尝试执行→失败重试→达到最大次数后抛出异常)。- 具体的业务逻辑由
callable.call()
实现,子类(或调用方)无需修改重试流程。
- 优势 :将可变部分(业务逻辑)和不变部分(重试流程)分离,符合开闭原则。
四、Retryer 的完整工作流程
通过上述分析,我们可以总结 Retryer
的工作流程如下:
- 初始化配置 :通过链式调用设置最大重试次数 (
maxAttempt
) 和待执行任务 (callable
)。 - 执行任务 :调用
retryCall()
触发实际执行。 - 重试逻辑 :
- 尝试执行
callable.call()
- 如果成功,直接返回结果
- 如果失败,检查是否达到最大重试次数
- 未达到则重新执行,否则抛出异常
- 尝试执行
五、如何扩展更复杂的重试策略?
实际项目中可能需要更灵活的策略,例如:
- 指数退避(Exponential Backoff):失败后等待时间逐渐增加(如 1s、2s、4s)。
- 条件重试:仅对特定异常(如网络超时)进行重试。
- 熔断机制:连续失败多次后暂停重试,避免雪崩效应。
这些功能可以通过在 Retryer
中添加以下方法实现:
java
Retryer.<SendResult>newInstance()
.retryIfException(e -> e instanceof SocketTimeoutException) // 条件过滤
.withBackoff(1, 10, TimeUnit.SECONDS) // 退避策略
.withFallback(() -> defaultResult) // 降级逻辑
六、总结:Retry 机制的设计哲学
- 关注点分离 :重试策略和业务逻辑分离,通过
Callable
封装命令。 - 开闭原则:通过模板方法固定流程,开放具体实现扩展。
- 防御式编程:通过有限次数的重试,提高系统容错能力。