接口重试的7种常用方案!

news/2025/3/26 3:34:19/文章来源:https://www.cnblogs.com/12lisu/p/18788852

前言

记得五年前的一个深夜,某个电商平台的订单退款接口突发异常,因为银行系统网络抖动,退款请求连续失败。

原本技术团队只是想“好心重试几次”,结果开发小哥写的重试代码竟疯狂调用了银行的退款接口 82次

最终导致用户账户重复退款,平台损失过百万。

老板在复盘会上质问:“接口重试这么基础的事,为什么还能捅出大篓子?”

大家哑口无言,因为所有人都以为只要加个 for 循环,再睡几秒就完事了……

这篇文章跟大家一起聊聊重试的7种常用方案,希望对你会有所帮助。

1 暴力轮回法

问题场景

某实习生写的用户注册短信发送接口。

在一个while循环中,重复调用第三方的发短信接口给用户发送短信。

代码如下:

public void sendSms(String phone) {int retry = 0;while (retry < 5) { // 无脑循环try {smsClient.send(phone);break;} catch (Exception e) {retry++;Thread.sleep(1000); // 固定1秒睡眠}}
}

事故现场

某次短信服务器出现了过载问题,导致所有请求都延迟了3秒。

这个暴力循环的代码在 0.5秒内同时发起数万次重试,直接打爆短信平台,触发了 熔断封禁,连正常请求也被拒绝。

教训

  • 💥 不做延迟间隔调整:固定间隔导致重试请求集中爆发
  • 💥 无视异常类型:非临时性错误(如参数错误)也尝试重试
  • 🔑 修复方案:加上随机的重试间隔,并过滤不可重试的异常

2 Spring Retry

应用场景

Spring Retry适用于中小项目,通过注解快速实现基本重试和熔断(如订单状态查询接口)。

通过声明@Retryable注解,来实现接口重试的功能。

配置示例

@Retryable(value = {TimeoutException.class}, // 只重试超时异常maxAttempts = 3,backoff = @Backoff(delay = 1000, multiplier = 2) // 1秒→2秒→4秒
)
public boolean queryOrderStatus(String orderId) {return httpClient.get("/order/" + orderId);
}@Recover // 兜底回退方法
public boolean fallback() {return false; 
}

优势

  • 声明式注解:代码简洁,与业务逻辑解耦
  • 指数退避:自动拉长重试间隔
  • 熔断集成:结合 @CircuitBreaker 可快速阻断异常流量

3 Resilience4j

高阶场景

对于有些需要自定义退避算法、熔断策略和多层防护的大中型系统(如支付核心接口),我们可以使用 Resilience4j。

核心代码如下:

// 1. 重试配置:指数退避 + 随机抖动
RetryConfig retryConfig = RetryConfig.custom().maxAttempts(3).intervalFunction(IntervalFunction.ofExponentialRandomBackoff(1000L, // 初始间隔1秒2.0,   // 指数倍数0.3    // 随机抖动系数)).retryOnException(e -> e instanceof TimeoutException).build();// 2. 熔断配置:错误率超50%时熔断
CircuitBreakerConfig cbConfig = CircuitBreakerConfig.custom().slidingWindow(10, 10, CircuitBreakerConfig.SlidingWindowType.COUNT_BASED) .failureRateThreshold(50).build();// 组合使用
Retry retry = Retry.of("payment", retryConfig);
CircuitBreaker cb = CircuitBreaker.of("payment", cbConfig);// 执行业务逻辑
Supplier<Boolean> supplier = () -> paymentService.pay();
Supplier<Boolean> decorated = Decorators.ofSupplier(supplier).withRetry(retry).withCircuitBreaker(cb).decorate();

效果

某电商大厂上线此方案后,支付接口 超时率下降60% ,且熔断触发频率降低近 90%

真正做到了“打不还手,骂不还口”。

4 MQ队列

适用场景

高并发、允许延时的异步场景(如物流状态同步)。

实现原理

  1. 首次请求失败后,将消息投递至 延时队列
  2. 队列根据预设的延时时间(如5秒、30秒、1分钟)重试消费
  3. 若达到最大重试次数,则转存至 死信队列(人工处理)

RocketMQ代码片段如下:

// 生产者发送延时消息
Message<String> message = new Message();
message.setBody("订单数据");
message.setDelayTimeLevel(3); // RocketMQ预设的10秒延迟级别
rocketMQTemplate.send(message);// 消费者重试
@RocketMQMessageListener(topic = "DELAY_TOPIC")
public class DelayConsumer {@Overridepublic void handleMessage(Message message) {try {syncLogistics(message);} catch (Exception e) {// 重试次数 + 1,并重新发送到更高延迟级别resendWithDelay(message, retryCount + 1);}}
}

如何RocketMQ的消费者消费失败,会自动发起重试。

5 定时任务

适用场景

对于有些不需要实时反馈,允许批量处理的任务(如文件导入)的业务场景,我们可以使用定时任务。

在这里以Quartz为例。

具体代码如下:

@Scheduled(cron = "0 0/5 * * * ?") // 每5分钟执行
public void retryFailedTasks() {List<FailedTask> list = failedTaskDao.listUnprocessed(5); // 查失败任务list.forEach(task -> {try {retryTask(task);task.markSuccess();} catch (Exception e) {task.incrRetryCount();}failedTaskDao.update(task);});
}

6 两阶段提交

适用场景

对于严格保证数据一致性的场景(如资金转账),我们可以使用两阶段提交机制。

关键实现

  1. 第一阶段:记录操作流水到数据库(状态为“进行中”)
  2. 第二阶段:调用远程接口,并根据结果更新流水状态
  3. 定时补偿:扫描超时的“进行中”流水重新提交

大致代码如下:

@Transactional
public void transfer(TransferRequest req) {// 1. 记录流水transferRecordDao.create(req, PENDING);// 2. 调用银行接口boolean success = bankClient.transfer(req);// 3. 更新流水状态transferRecordDao.updateStatus(req.getId(), success ? SUCCESS : FAILED);// 4. 失败转异步重试if (!success) {mqTemplate.send("TRANSFER_RETRY_QUEUE", req);}
}

7 分布式锁

应用场景

对于一些多服务实例、多线程环境的防重复提交(如秒杀)的业务场景,我们可以使用分布式锁。

这里以Redis + Lua的分布式锁为例。

代码如下:

public boolean retryWithLock(String key, int maxRetry) {String lockKey = "api_retry_lock:" + key;for (int i = 0; i < maxRetry; i++) {// 尝试获取分布式锁if (redis.setnx(lockKey, "1", 30, TimeUnit.SECONDS)) {try {return callApi();} finally {redis.delete(lockKey);}}Thread.sleep(1000 * (i + 1)); // 等待释放锁}return false;
}

总结

重试就像机房里的灭火器——永远不希望用到它,但必须保证关键时刻能救命。

我们工作中选择哪种方案?

别只看技术潮流,而要看业务的长矛和盾牌,需要哪种配合。

最后送大家一句话:系统稳定的秘诀,是永远对重试保持敬畏。

最后说一句(求关注,别白嫖我)

如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下我的同名公众号:苏三说技术,您的支持是我坚持写作最大的动力。

求一键三连:点赞、转发、在看。

关注公众号:【苏三说技术】,在公众号中回复:进大厂,可以免费获取我最近整理的10万字的面试宝典,好多小伙伴靠这个宝典拿到了多家大厂的offer。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/904690.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大型通用电子制造执行系统(MES)

​简介:系统参考西门子MOM智能制造Opcenter工业软件制造执行系统Camstar电子套件人机料法环数据建模业务对象和车间生产执行事务逻辑,采用面向对象分层设计与C#编程开发:包含电子制造企业人机料法环业务数据建模实体对象,数据实体持久化映射,数据工厂会话配置,车间生产服务…

物料需求波动大、生产计划变化频繁?一文教你用工厂ERP系统MRP精确计算生产物料!

今天咱们来聊一聊一个大多数工厂都会遇到的问题: 物料需求波动大 和 生产计划变化频繁 。 这些问题可能让你在生产过程中经常“踩雷”,比如物料没采购够,生产停工; 或者物料买多了,库存积压,浪费了钱。有没有办法避免这些问题呢? 答案是: 有 !那就是通过工厂的 ERP系统…

docker使用GPU总结

在docker容器中使用显卡 一 docker19.03以前的事情 1.1 指定显卡硬件名 最初的容器中使用显卡,需要指定硬件名。经历了两种方式使用lxc驱动程序运行docker守护进程,以便能够修改配置并让容器访问显卡设备(非常麻烦,参考链接中最久远的回答)Docker 0.9中放弃了lxc作为默认执…

掌握 K8s Pod 基础应用 (二)

Pod生命周期 我们一般将pod对象从创建至终的这段时间范围称为pod的生命周期,它主要包含下面的过程:pod创建过程运行初始化容器(init container)过程运行主容器(main container)容器启动后钩子(post start)、容器终止前钩子(pre stop)容器的存活性探测(liveness prob…

叉车人员防撞报警系统

叉车人员防撞报警系统采用机器视觉图像感知技术,通过人工智能深度学习技术,对行人和车辆的精确检测,叉车人员防撞报警系统实现对人体和车辆检测分析识别,在机器视觉图像景中,通过特征识别算法建立人体和车辆图像模型,完成自动识别目标,并能以视觉图像智能分析精确区分干…

课堂在线点名助手

适合老师上课随机提问学生的小工具。前情概要 为提高程序的普适性,学生姓名用学号代替。 在线点卯在线提问点名助手body0 { font-family: Arial, sans-serif; background-image: url(https://img2024.cnblogs.com/blog/992978/202503/992978-20250324092106498-1518746661.jpg…

随堂笔记之Java方法

此内容源自B站狂神说Java基础的课后笔记与总结,用于复习和查看,因此写得比较简陋,不太适合萌新学习 方法定义方法调用 调用方法:对象名.方法名(实参列表)方法返回一个值的时候,方法调用通常被当做一个值 方法返回值是void,方法调用是一条语句*值传递和引用传递: Java全…

3.24

Android Studio 启动模拟器出现“Timed out after 300seconds waiting for emulator to come online“解决方案 问题: Timed out after 300seconds waiting for emulator to come online.**解决方法: 检查自己的Emulator是否是最新版本,如果不是检查更新到最新

使用ESP32的语音到文本转换

ESP32 + INMP441麦克风+ SD卡+ Deepgram API =语音到文本只需3秒!即时记录、存储和转录。现在就试试! 最快!使用ESP32板的语音到文本转换语音转文本技术改变了许多项目的游戏规则。从在智能家居中实现免提控制到为残疾人创建无障碍解决方案,将口语转换为文本的能力打开了无限的…

SNeP软件计算有效群体大小

001、官网: https://sourceforge.net/projects/snepnetrends/002、脚本: SNeP1.1 -ped sample_name.ped -map sample_name.map -threads 10 -ld -out ./sample_name003、结果文件:。

2025.3.28(周五)

android实验二第三部分 事情处理 1. 点击事件 (OnClickListener) 方法:setOnClickListener(View.OnClickListener listener) android:onClick(在 XML 中使用) 核心功能:处理按钮、文本、图片等控件的 单击事件。 使用场景:按钮提交、页面跳转、功能触发等。 2. 长按事件 (…

关于原生小程序canvas标签始终显示再最上层问题

官方说明: https://developers.weixin.qq.com/miniprogram/dev/component/native-component.html 解决方法:将canvas正常绘制 对canvas进行定位,远离手机屏幕显示区域 将canvas转换为img图片,将转换后的图片进行展示 如果图片清晰度不够,则将canvas成倍的绘制,将转换后的…