MQ项目接入Retry实践——构造器模式+命令模式+模版方法模式


一、为什么需要重试机制?

在实际开发中,网络请求、数据库操作、远程服务调用等场景都可能因瞬时故障 (如网络抖动、服务短暂不可用)导致失败。重试机制的核心思想是:通过有限次数的重复尝试,提高操作最终成功的概率。这就像打电话时对方占线,我们会选择稍后再拨,而不是直接放弃。


二、代码案例中的关键语法现象

让我们先观察代码片段:

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 的工作流程如下:

  1. 初始化配置 :通过链式调用设置最大重试次数 (maxAttempt) 和待执行任务 (callable)。
  2. 执行任务 :调用 retryCall() 触发实际执行。
  3. 重试逻辑
    • 尝试执行 callable.call()
    • 如果成功,直接返回结果
    • 如果失败,检查是否达到最大重试次数
    • 未达到则重新执行,否则抛出异常

五、如何扩展更复杂的重试策略?

实际项目中可能需要更灵活的策略,例如:

  1. 指数退避(Exponential Backoff):失败后等待时间逐渐增加(如 1s、2s、4s)。
  2. 条件重试:仅对特定异常(如网络超时)进行重试。
  3. 熔断机制:连续失败多次后暂停重试,避免雪崩效应。

这些功能可以通过在 Retryer 中添加以下方法实现:

java 复制代码
Retryer.<SendResult>newInstance()
    .retryIfException(e -> e instanceof SocketTimeoutException) // 条件过滤
    .withBackoff(1, 10, TimeUnit.SECONDS) // 退避策略
    .withFallback(() -> defaultResult) // 降级逻辑

六、总结:Retry 机制的设计哲学

  1. 关注点分离 :重试策略和业务逻辑分离,通过 Callable 封装命令。
  2. 开闭原则:通过模板方法固定流程,开放具体实现扩展。
  3. 防御式编程:通过有限次数的重试,提高系统容错能力。
相关推荐
狗头大军之江苏分军2 分钟前
存钱 vs 投资 vs 提升自己:哪个才是打工人的“出路
前端·javascript·后端
用户75828121830739 分钟前
什么?分不清Go语言中空切片与nil切片竟然导致这么多线上问题?
后端
_風箏20 分钟前
Java【代码 18】处理Word文档里的Excel表格数据(源码分享)
后端
_風箏22 分钟前
Java【代码 19】含有换行符\r\n的字符串匹配(源码分享)处理Word文档里的Excel表格数据
后端
码事漫谈26 分钟前
C++11 std::function 详解:通用多态函数包装器
后端
森码29 分钟前
HTTPS自动更新策略
后端
码事漫谈33 分钟前
在macOS上使用VS Code和Clang配置C++开发环境
后端
南玖yy1 小时前
C++多态:面向对象编程的灵魂之
运维·开发语言·数据库·c++·后端·c·c语音
shepherd1111 小时前
从List与Tree相互转换工具类实现中谈谈菜鸟到老鸟的一些思考
java·后端·代码规范
优创学社21 小时前
Springboot社区养老保险系统小程序
java·spring boot·后端