指数退避算法& spring retry

本文介绍了Spring框架中的Retryable注解和指数退避算法在处理网络请求失败时的应用,包括示例代码和如何配置重试策略。重点讲解了如何通过maxAttempts和exponentialBackoff参数来控制重试次数和延迟增长规律。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

指数退避算法

本节转载自: https://cloud.google.com/memorystore/docs/redis/exponential-backoff?hl=zh-cn

指数退避算法是适用于网络应用的标准错误处理策略,使用这种策略时,客户端会定期重试失败的请求,并不断增加各次请求之间的延迟时间。

示例算法

指数退避算法以指数方式重试请求(不断增加各次重试之间的等待时间,直到达到最大退避时间)。示例如下:

  1. 客户端发出请求。
  2. 如果请求失败,请等待 1 + random_number_milliseconds 秒后再重试请求。
  3. 如果请求失败,请等待 2 + random_number_milliseconds 秒后再重试请求。
  4. 如果请求失败,请等待 4 + random_number_milliseconds 秒后再重试请求。
  5. 依此类推,等待时间上限为 maximum_backoff

等待时间达到上限后,您可以继续等待并重试,直到达到重试次数上限(但接下来的重试操作不会增加各次重试之间的等待时间)。

其中:

  • 等待时间为 min(((2^n)+random_number_milliseconds), maximum_backoff),其中,n 会在每次迭代(请求)后增加 1。
  • random_number_milliseconds 是小于或等于 1000 的毫秒数(随机值)。这有助于避免出现以下情况:许多客户端在某些情况下全部同步进行处理并同时执行重试操作,导致同步发送每一波请求。每次重试请求后,系统都应重新计算 random_number_milliseconds 值。
  • maximum_backoff 通常为 32 或 64 秒。哪个值更为适当,这取决于用例。
  • 达到 maximum_backoff 时间后,您可以继续重试。 此后执行的重试不需要继续增加退避时间。
    • 例如,如果客户端使用的 maximum_backoff 时间为 64 秒,则在达到此值后,客户端可以每 64 秒重试一次。在某个时刻,应阻止客户端无限重试。


Spring 退避算法

@Retryable

引用:https://www.baeldung.com/spring-retry

maven依赖

   <dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</artifactId>
            <version>1.2.5.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.2.8.RELEASE</version>
        </dependency>

java code

@Service
public class OrderService {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

	    private AtomicInteger loop = new AtomicInteger(1);
    private Instant instantBefore;
    @Retryable(
            value = RuntimeException.class , //抛出该异常时,进行重试
            maxAttempts=12,  // 最大重试次数,超过直接抛出异常
            backoff =
            @Backoff(
                    delay=500, //初始delay时间
                    multiplier = 2, // 设置为2时,可以理解为
                    maxDelay=10000, // min(delay * multiplier^(attempt - 1), maxDelay)
                    random =  false //是否使用随机数
            )
    )
    public void readOrder(String orderId){
        Instant now = Instant.now();
        long  gap =  instantBefore == null ? 0 : Duration.between(instantBefore, now).toMillis();
        logger.info("{} --- time-gap :{}",loop.getAndIncrement(),gap);
        instantBefore = now;

        if( 1 < 2) throw new RuntimeException(); // 抛出异常,进行重试
    }
}

上述算法可以描述为: min(delay * multiplier^(attempt - 1), maxDelay),

multiplier=2 && random=true,可以近似理解为指数退避算法

执行结果

在这里插入图片描述

RetryTemplate


    public void readOrder(String orderId){
        int retryCount = 3;
        RetryTemplate retryTemplate = RetryTemplate.builder()
                .maxAttempts(retryCount)
            //    .uniformRandomBackoff(10000, 15000)
                .exponentialBackoff(500,2, 10000)
                .retryOn(RetryException.class) //抛出该异常时,进行重试
                .build();


        retryTemplate.execute(context -> {
            System.out.println("xx");
            return null;
        });
    }

awaitility

https://blog.youkuaiyun.com/it_freshman/article/details/142953190

&lt;think&gt;好的,我现在需要帮用户设计一个用Spring Boot实现的可靠文件传输系统,包含失败补传机制。用户之前已经得到了一个分步骤的方案,现在需要具体用Spring Boot技术来实现。首先,我得回顾一下之前的方案,确保不遗漏关键点。 用户的需求是从一个服务器下载文件再传到另一个服务器,需要考虑失败补传。之前的方案提到了分块传输、校验、状态记录、重试机制等。现在需要用Spring Boot来实现这些功能。 首先,我需要考虑Spring Boot的常用组件和库。比如,文件传输可以用RestTemplate或者WebClient,但大文件的话可能需要分块处理。数据库方面,可以用JPA或者Spring Data来存储传输状态。定时任务可以用@Scheduled来做重试。校验的话,可能需要用MD5或SHA算法Java自带的MessageDigest应该可以。 接下来,结构设计。用户可能需要模块化的设计,比如分成几个模块:传输任务管理、分块处理、状态存储、重试机制、校验模块等。用Spring Boot的话,可以分不同的Service类来处理不同的功能。 然后,分块传输的具体实现。下载和上传都需要支持断点续传,所以每个块的状态需要记录。数据库表设计可能需要记录任务ID、文件名、当前块序号、总块数、状态等。每次传输一个块后更新状态,如果失败,下次可以从该块继续。 状态机设计,之前有提到状态图,可能需要用枚举来表示不同的传输状态,比如初始化、下载中、校验中、上传中、完成、失败等。状态转换需要处理得当,可能用状态模式或者简单的条件判断。 异常处理和重试机制是关键。Spring Retry库可以帮助实现方法级别的重试,结合@Retryable注解。还需要自定义重试策略,比如指数退避,避免频繁重试导致服务器压力过大。对于不同的异常类型,比如网络超时、校验失败,需要不同的重试策略。 校验部分,每个块下载后需要计算MD5,与预期的对比。如果不匹配,触发重试。整个文件传输完成后,还需要整体校验一次,确保完整性。 日志和监控也很重要。需要记录每个任务的详细日志,方便排查问题。可以用Spring Boot Actuator来集成健康检查,或者用Prometheus和Grafana做监控。 安全性方面,传输过程中可能需要加密,比如使用HTTPS。存储数据库中的敏感信息,比如服务器凭证,需要用加密算法保存,避免明文存储。 测试部分,需要单元测试和集成测试。Mock服务器来模拟源和目标服务器,测试各种失败场景,比如网络中断、服务宕机,确保重试和续传机制正常工作。 可能遇到的挑战包括大文件分块的内存管理,避免内存溢出,应该用流式处理而不是全部读入内存。另外,分布式环境下如何处理并发传输,是否需要锁机制,或者任务队列。 工具选型方面,除了Spring Boot自带的库,可能需要用到Apache HttpClient处理分块下载,或者使用AsyncRestTemplate进行异步传输提高效率。数据库可以用H2内存数据库做测试,生产环境用MySQL或PostgreSQL。 最后,部署和性能优化。可能需要配置连接池,调整线程池参数,处理高并发情况。动态分块大小根据网络状况调整,可能需要监控网络带宽和延迟,实时调整块大小。 总结下来,需要分步骤实现,从任务创建、分块下载、校验、上传、状态更新、异常处理到日志监控,每个环节都要考虑周全,利用Spring Boot的特性和相关库来简化开发&lt;/think&gt;以下为基于Spring Boot的可靠文件传输系统实现方案,采用模块化设计和分层架构: --- ### 一、技术架构设计 ```mermaid graph TD A[API层] --&gt; B[服务层] B --&gt; C[传输引擎] C --&gt; D[分块处理器] C --&gt; E[校验模块] C --&gt; F[状态管理器] G[数据库] --&gt; F H[定时任务] --&gt; B ``` --- ### 二、核心模块实现 #### 1. 领域模型设计 ```java @Entity public class TransferTask { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String taskId; // UUID private String sourceUrl; // 源地址 private String targetUrl; // 目标地址 private String filename; // 文件名 private Long fileSize; // 文件大小 private Integer chunkSize = 10 * 1024 * 1024; // 分块大小 private Integer currentChunk; // 当前传输块 private String checksum; // 整体校验码 private TaskStatus status; // 任务状态 } public enum TaskStatus { INITIALIZED, DOWNLOADING, UPLOADING, VERIFYING, COMPLETED, FAILED } ``` #### 2. 分块传输服务 ```java @Service public class ChunkTransferService { // 分块下载实现 @Retryable(value = {IOException.class}, maxAttempts = 3, backoff = @Backoff(delay = 5000)) public byte[] downloadChunk(String url, long start, int chunkSize) { HttpHeaders headers = new HttpHeaders(); headers.setRange(List.of(HttpRange.createByteRange(start, start + chunkSize - 1))); return restTemplate.exchange( url, HttpMethod.GET, new HttpEntity&lt;&gt;(headers), byte[].class ).getBody(); } // 分块上传实现 public void uploadChunk(String targetUrl, byte[] chunk, int chunkIndex) { MultiValueMap&lt;String, Object&gt; body = new LinkedMultiValueMap&lt;&gt;(); body.add(&quot;file&quot;, new ByteArrayResource(chunk) { @Override public String getFilename() { return &quot;chunk_&quot; + chunkIndex; } }); restTemplate.postForObject(targetUrl, body, Void.class); } } ``` #### 3. 状态管理服务 ```java @Service @Transactional public class TransferStateService { @Autowired private TransferTaskRepository repository; // 原子操作更新传输进度 @Modifying @Query(&quot;UPDATE TransferTask t SET t.currentChunk = :current WHERE t.taskId = :taskId&quot;) void updateProgress(@Param(&quot;taskId&quot;) String taskId, @Param(&quot;current&quot;) int currentChunk); // 获取待恢复任务 public List&lt;TransferTask&gt; findResumableTasks() { return repository.findByStatusIn( Arrays.asList(TaskStatus.DOWNLOADING, TaskStatus.UPLOADING) ); } } ``` --- ### 三、断点续传流程实现 #### 1. 核心传输流程 ```java public void executeTransfer(TransferTask task) { try { // 断点续传起点 int startChunk = task.getCurrentChunk() != null ? task.getCurrentChunk() : 0; for (int chunkIndex = startChunk; chunkIndex &lt; totalChunks; chunkIndex++) { // 下载分块 byte[] chunk = chunkService.downloadChunk( task.getSourceUrl(), chunkIndex * task.getChunkSize(), task.getChunkSize() ); // 校验分块 verifyChunk(chunk, task.getChecksumMap().get(chunkIndex)); // 上传分块 chunkService.uploadChunk(task.getTargetUrl(), chunk, chunkIndex); // 更新进度 stateService.updateProgress(task.getTaskId(), chunkIndex); } // 合并分块 mergeChunks(task); } catch (Exception e) { stateService.markFailed(task.getTaskId()); throw new TransferException(&quot;文件传输中断&quot;, e); } } ``` #### 2. 校验模块实现 ```java public class VerificationService { // 分块校验 public void verifyChunk(byte[] data, String expectedMd5) { String actualMd5 = DigestUtils.md5DigestAsHex(data); if (!actualMd5.equals(expectedMd5)) { throw new VerificationException(&quot;分块校验失败&quot;); } } // 整体校验 public boolean verifyEntireFile(File file, String expectedChecksum) { try (InputStream is = new FileInputStream(file)) { return DigestUtils.md5DigestAsHex(is).equals(expectedChecksum); } } } ``` --- ### 四、异常处理与重试机制 #### 1. 全局异常处理 ```java @ControllerAdvice public class TransferExceptionHandler { @ExceptionHandler(ConnectException.class) public ResponseEntity&lt;ErrorResponse&gt; handleNetworkError(ConnectException ex) { return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE) .body(new ErrorResponse(&quot;NETWORK_ERROR&quot;, &quot;网络连接失败&quot;)); } @ExceptionHandler(VerificationException.class) public ResponseEntity&lt;ErrorResponse&gt; handleVerificationError(VerificationException ex) { return ResponseEntity.status(HttpStatus.BAD_REQUEST) .body(new ErrorResponse(&quot;CHECKSUM_MISMATCH&quot;, &quot;数据校验失败&quot;)); } } ``` #### 2. 重试配置类 ```java @Configuration @EnableRetry public class RetryConfig { @Bean public RetryTemplate retryTemplate() { RetryTemplate template = new RetryTemplate(); // 指数退避策略 ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy(); backOffPolicy.setInitialInterval(1000); backOffPolicy.setMultiplier(2); backOffPolicy.setMaxInterval(10000); // 简单重试策略 SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(); retryPolicy.setMaxAttempts(5); template.setRetryPolicy(retryPolicy); template.setBackOffPolicy(backOffPolicy); return template; } } ``` --- ### 五、监控与日志增强 #### 1. 传输指标监控 ```java @Configuration public class MetricsConfig { @Bean public MeterRegistryCustomizer&lt;MeterRegistry&gt; transferMetrics() { return registry -&gt; { Counter.builder(&quot;transfer.chunks&quot;) .tag(&quot;type&quot;, &quot;download&quot;) .register(registry); Timer.builder(&quot;transfer.duration&quot;) .publishPercentiles(0.5, 0.95) .register(registry); }; } } ``` #### 2. 日志追踪实现 ```java @Aspect @Component public class TransferLoggingAspect { @Around(&quot;execution(* com.example.transfer.service.*Service.*(..))&quot;) public Object logServiceMethod(ProceedingJoinPoint joinPoint) throws Throwable { String methodName = joinPoint.getSignature().getName(); MDC.put(&quot;transactionId&quot;, UUID.randomUUID().toString()); try { log.info(&quot;开始执行方法: {}&quot;, methodName); long start = System.currentTimeMillis(); Object result = joinPoint.proceed(); long duration = System.currentTimeMillis() - start; log.info(&quot;方法执行完成: {} (耗时 {}ms)&quot;, methodName, duration); return result; } catch (Exception e) { log.error(&quot;方法执行异常: {}&quot;, methodName, e); throw e; } finally { MDC.clear(); } } } ``` --- ### 六、部署与测试建议 #### 1. 性能优化配置 ```yaml # application.yml spring: servlet: multipart: max-file-size: 10GB max-request-size: 10GB datasource: hikari: maximum-pool-size: 20 connection-timeout: 30000 custom: transfer: chunk-size: ${CHUNK_SIZE:10485760} # 10MB max-retries: 5 ``` #### 2. 集成测试用例 ```java @SpringBootTest class FileTransferApplicationTests { @MockBean private RestTemplate restTemplate; @Test void testResumeTransfer() { // 模拟中断场景 when(restTemplate.exchange(any(), any(), any(), eq(byte[].class))) .thenThrow(new ResourceAccessException(&quot;模拟网络中断&quot;)) .thenReturn(ResponseEntity.ok(new byte[1024])); // 验证重试逻辑 assertThatThrownBy(() -&gt; transferService.executeTransfer(task)) .isInstanceOf(TransferException.class); // 验证状态更新 TransferTask updated = stateService.getTask(taskId); assertThat(updated.getStatus()).isEqualTo(TaskStatus.FAILED); } } ``` --- ### 七、扩展建议 1. **分布式支持**:结合Spring Cloud实现集群部署 2. **加密传输**:集成Spring Security OAuth2进行传输加密 3. **云原生适配**:增加S3、OSS等云存储适配器 4. **可视化监控**:集成Prometheus+Grafana展示传输指标 5. **智能分块**:根据网络质量动态调整分块大小 通过以上实现方案,可以构建一个具备企业级可靠性的文件传输系统,满足断点续传、自动重试、数据校验等核心需求。实际开发时建议采用feature分支开发模式,结合CI/CD流程确保系统稳定性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值