找回密码
 立即注册
首页 业界区 业界 瞧瞧别人家的接口重试,那叫一个优雅! ...

瞧瞧别人家的接口重试,那叫一个优雅!

任娅翠 昨天 19:32
前言

2025年某电商平台深夜故障,因重试策略不当导致银行退款接口被调用82次,引发重复退款126万元
复盘发现:80%的开发者认为重试就是for循环+Thread.sleep(),却忽略了重试风暴幂等性缺失资源雪崩等致命问题。
这篇文章跟大家一起聊聊接口重试的8种常用方案,希望对你会有所帮助。
一、重试机制的原因

1.1 为什么需要重试?

1.png

临时性故障占比超70%,合理重试可将成功率提升至99%以上。
1.2 重试的三大陷阱


  • 重试风暴:固定间隔重试引发请求洪峰(如万次重试压垮服务)
  • 数据不一致:非幂等操作导致重复生效(如重复扣款)
  • 链路阻塞:长时重试耗尽线程资源(如数据库连接池枯竭)
二、基础重试方案

2.1 暴力轮回法(青铜)

问题代码
  1. // 危险!切勿直接用于生产!
  2. public void sendSms(String phone) {
  3.     int retry = 0;
  4.     while (retry < 5) {
  5.         try {
  6.             smsClient.send(phone);
  7.             break;
  8.         } catch (Exception e) {
  9.             retry++;
  10.             Thread.sleep(1000); // 固定1秒间隔
  11.         }
  12.     }
  13. }
复制代码
事故案例:某平台短信接口重试风暴,触发第三方熔断封禁。
优化方向:增加随机抖动 + 异常过滤。
2.2 Spring Retry(黄金)

声明式注解控制重试
  1. @Retryable(
  2.     value = {TimeoutException.class}, // 仅重试超时异常
  3.     maxAttempts = 3,
  4.     backoff = @Backoff(delay = 1000, multiplier = 2) // 指数退避:1s→2s→4s
  5. )
  6. public boolean queryOrder(String orderId) {
  7.     return httpClient.get("/order/" + orderId);
  8. }
  9. @Recover // 兜底降级
  10. public boolean fallback(TimeoutException e) {
  11.     return false;
  12. }
复制代码
优势

  • 注解驱动,业务零侵入
  • 支持指数退避策略
  • 无缝集成熔断器@CircuitBreaker
三、高阶重试方案

3.1 Resilience4j(白金)

应对高并发场景的重试+熔断组合拳
  1. // 重试配置:指数退避+随机抖动
  2. RetryConfig retryConfig = RetryConfig.custom()
  3.     .maxAttempts(3)
  4.     .intervalFunction(IntervalFunction.ofExponentialRandomBackoff(
  5.         1000L, 2.0, 0.3 // 初始1s,指数倍率2,抖动率30%
  6.     ))
  7.     .retryOnException(e -> e instanceof TimeoutException)
  8.     .build();
  9. // 熔断配置:错误率超50%触发熔断
  10. CircuitBreakerConfig cbConfig = CircuitBreakerConfig.custom()
  11.     .slidingWindow(10, 10, COUNT_BASED)
  12.     .failureRateThreshold(50)
  13.     .build();
  14. // 组合装饰
  15. Supplier<Boolean> supplier = () -> paymentService.pay();
  16. Supplier<Boolean> decorated = Decorators.ofSupplier(supplier)
  17.     .withRetry(Retry.of("payment", retryConfig))
  18.     .withCircuitBreaker(CircuitBreaker.of("payment", cbConfig))
  19.     .decorate();
复制代码
效果:某支付系统接入后超时率下降60%,熔断触发率降低90%
3.2 Guava-Retrying(钻石)

灵活定制复杂重试逻辑
  1. Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
  2.     .retryIfResult(Predicates.equalTo(false)) // 返回false重试
  3.     .retryIfExceptionOfType(IOException.class)
  4.     .withWaitStrategy(WaitStrategies.exponentialWait(1000, 30, TimeUnit.SECONDS))
  5.     .withStopStrategy(StopStrategies.stopAfterAttempt(5))
  6.     .build();
  7. retryer.call(() -> uploadService.upload(file)); // 执行
复制代码
核心能力

  • 支持结果/异常双模式触发
  • 提供7种等待策略(随机、指数、递增等)
  • 可监听每次重试事件
四、分布式重试方案

4.1 MQ延时队列(星耀Ⅰ)

适用场景:异步解耦的高并发系统(如物流状态同步)
架构原理
2.png

RocketMQ实现
  1. // 生产者发送延时消息
  2. Message msg = new Message();
  3. msg.setBody(orderData);
  4. msg.setDelayTimeLevel(3); // RocketMQ预设10秒延迟
  5. rocketMQTemplate.send(msg);
  6. // 消费者
  7. @RocketMQMessageListener(topic = "RETRY_TOPIC")
  8. public class RetryConsumer {
  9.     public void consume(Message msg) {
  10.         try {
  11.             process(msg);
  12.         } catch (Exception e) {
  13.             // 提升延迟级别重发
  14.             msg.setDelayTimeLevel(5);
  15.             resend(msg);
  16.         }
  17.     }
  18. }
复制代码
优势

  • 重试与业务逻辑解耦
  • 天然支持梯度延时
  • 死信队列兜底人工处理
4.2 定时任务补偿(星耀Ⅱ)

适用场景:允许延迟的批处理任务(如文件导入)
  1. @Scheduled(cron = "0 0/5 * * * ?") // 每5分钟执行
  2. public void retryFailedTasks() {
  3.     List<FailedTask> tasks = taskDao.findFailed(MAX_RETRY);
  4.     tasks.forEach(task -> {
  5.         if (retry(task)) {
  6.             task.markSuccess();
  7.         } else {
  8.             task.incrRetryCount();
  9.         }
  10.         taskDao.update(task);
  11.     });
  12. }
复制代码
关键点

  • 数据库记录失败任务
  • 低峰期批量处理
  • 独立线程池隔离资源
4.3 两阶段提交(王者Ⅰ)

金融级一致性保障(如转账)
  1. @Transactional
  2. public void transfer(TransferRequest req) {
  3.     // 阶段1:持久化操作流水
  4.     TransferRecord record = recordDao.create(req, PENDING);
  5.    
  6.     // 阶段2:调用银行接口
  7.     boolean success = bankClient.transfer(req);
  8.    
  9.     // 更新状态
  10.     recordDao.updateStatus(record.getId(), success ? SUCCESS : FAILED);
  11.    
  12.     if (!success) {
  13.         mqTemplate.send("TRANSFER_RETRY_QUEUE", req); // 触发异步重试
  14.     }
  15. }
  16. // 补偿任务(扫描挂起流水)
  17. @Scheduled(fixedRate = 30000)
  18. public void compensate() {
  19.     List<TransferRecord> pendings = recordDao.findPending(30);
  20.     pendings.forEach(this::retryTransfer);
  21. }
复制代码
核心思想操作前先留痕,任何失败可追溯
4.4 分布式锁重试(王者Ⅱ)

防重复提交终极方案(如秒杀)
  1. public boolean retryWithLock(String key, int maxRetry) {
  2.     String lockKey = "RETRY_LOCK:" + key;
  3.     for (int i = 0; i < maxRetry; i++) {
  4.         if (redis.setIfAbsent(lockKey, "1", 30, SECONDS)) {
  5.             try {
  6.                 return callApi(); // 持有锁时执行
  7.             } finally {
  8.                 redis.delete(lockKey);
  9.             }
  10.         }
  11.         Thread.sleep(1000 * (i + 1)); // 等待锁释放
  12.     }
  13.     return false;
  14. }
复制代码
适用场景

  • 多实例部署环境
  • 高竞争资源访问
  • 等幂性要求极高业务
五、响应式重试:Spring WebFlux方案

5.1 响应式重试操作符
  1. Mono<String> remoteCall = Mono.fromCallable(() -> {
  2.     if (Math.random() > 0.5) throw new RuntimeException("模拟失败");
  3.     return "Success";
  4. });
  5. remoteCall.retryWhen(Retry.backoff(3, Duration.ofSeconds(1))
  6.           .doBeforeRetry(signal -> log.warn("第{}次重试", signal.totalRetries()))
  7.           .subscribe();
复制代码
策略支持

  • 指数退避:Retry.backoff(maxAttempts, firstBackoff)
  • 随机抖动:.jitter(0.5)
  • 条件过滤:.filter(ex -> ex instanceof TimeoutException)
六、重试的避坑指南

6.1 必须实现的三大防护

防护类型目的实现方案幂等性防护防止重复生效唯一ID+状态机重试风暴防护避免洪峰冲击指数退避+随机抖动资源隔离保护主链路资源线程池隔离/熔断器6.2 经典踩坑案例


  • 坑1:无限制重试
    → 某系统因未设重试上限,线程池爆满导致集群雪崩
    解法:maxAttempts=3 + 熔断降级
  • 坑2:忽略错误类型
    → 参数错误(4xx)被反复重试,放大无效流量
    解法:retryOnException(e -> e instanceof TimeoutException)
  • 坑3:上下文丢失
    → 异步重试后丢失用户会话信息
    解法:重试前快照关键上下文(如userId、requestId)
七、方案选型参考图

3.png

总结


  • 敬畏每一次重试:重试不是暴力补救,而是精密流量控制。
  • 面向失败设计:假设网络不可靠、服务会宕机、资源终将枯竭。
  • 分层防御体系

    • 代码层:幂等性 + 超时控制
    • 框架层:退避策略 + 熔断降级
    • 架构层:异步解耦 + 持久化补偿

  • 没有银弹:秒杀场景用分布式锁,支付系统用两阶段提交,IoT设备用MQTT重试机制。
正如分布式系统大师Leslie Lamport所言:“重试是分布式系统的成人礼”
掌握这8种方案,你将拥有让系统“起死回生”的魔法!
最后说一句(求关注,别白嫖我)

如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下我的同名公众号:苏三说技术,您的支持是我坚持写作最大的动力。
求一键三连:点赞、转发、在看。
关注公众号:【苏三说技术】,在公众号中回复:进大厂,可以免费获取我最近整理的10万字的面试宝典,好多小伙伴靠这个宝典拿到了多家大厂的offer。
本文收录于我的技术网站:http://www.susan.net.cn

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册