建视频网站系统,性价比最高网站建设,网站建设方案书 个人网站,怎样自己开发一款软件问题背景 在现代的分布式系统中#xff0c;服务间的调用往往需要处理各种网络异常、超时等问题。重试机制是一种常见的解决策略#xff0c;它允许应用程序在网络故障或临时性错误后自动重新尝试失败的操作。Spring Boot 提供了灵活的方式来集成重试机制#xff0c;这可以通过…问题背景 在现代的分布式系统中服务间的调用往往需要处理各种网络异常、超时等问题。重试机制是一种常见的解决策略它允许应用程序在网络故障或临时性错误后自动重新尝试失败的操作。Spring Boot 提供了灵活的方式来集成重试机制这可以通过使用 Spring Retry 模块来实现。本文将通过一个具体的使用场景来详细介绍如何在 Spring Boot 应用中集成和使用 Spring Retry 技术。 场景描述
假如我们正在开发一个OMS系统(订单管理系统)其中一个关键服务 OrderService 负责订单创建和调用WMS服务扣减库存API 。由于网络不稳定或外部 API 可能暂时不可用我们需要在这些情况下实现重试机制以确保请求的成功率和系统的稳定性。
实现步骤
1. 添加依赖
首先在你的 pom.xml 文件中添加 Spring Retry 和 AOP 的依赖
dependencies!-- Spring Boot Starter Web --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency!-- Spring Retry --dependencygroupIdorg.springframework.retry/groupIdartifactIdspring-retry/artifactId/dependency!-- Spring Boot Starter AOP --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-aop/artifactId/dependency/dependencies
2. 创建配置类
创建一个配置类来配置重试模板
package com.zlp.retry.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
/*** 全局重试配置*/
Configuration
public class CustomRetryConfig {/**** 这段代码定义了一个 CustomRetryConfig 类其中包含一个 retryTemplate 方法。该方法用于创建并配置一个 RetryTemplate 对象该对象用于处理重试逻辑。** 1. 创建 RetryTemplate 实例**创建一个 RetryTemplate 对象。* 2. 设置重试策略使用 SimpleRetryPolicy 设置最大重试次数为5次。* 3. 设置延迟策略使用 ExponentialBackOffPolicy 设置初始延迟时间为1000毫秒每次重试间隔时间乘以2。* 4. 应用策略将重试策略和延迟策略应用到 RetryTemplate 对象。*/Beanpublic RetryTemplate retryTemplate() {RetryTemplate template new RetryTemplate();// 设置重试策略SimpleRetryPolicy policy new SimpleRetryPolicy();policy.setMaxAttempts(5);// 设置延迟策略ExponentialBackOffPolicy backOffPolicy new ExponentialBackOffPolicy();backOffPolicy.setInitialInterval(1000);backOffPolicy.setMultiplier(2.0);template.setRetryPolicy(policy);template.setBackOffPolicy(backOffPolicy);return template;}
}3. 创建服务类
创建一个服务类 OrderService和接口实现类OrderServiceImpl并在需要重试的方法上使用 Retryable 注解
/*** Classname OrderService* Date 2024/11/18 21:03* Created by ZouLiPing*/
public interface OrderService {/*** 创建订单* param createOrderReq* return*/String createOrder(CreateOrderReq createOrderReq);
}
package com.zlp.retry.service.impl;import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.zlp.retry.dto.CreateOrderReq;
import com.zlp.retry.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;import java.util.UUID;/*** Classname OrderServiceImpl* Date 2024/11/18 21:06* Created by ZouLiPing*/
Service
Slf4j(topic OrderServiceImpl)
public class OrderServiceImpl implements OrderService {OverrideRetryable(value {Exception.class},maxAttempts 4, backoff Backoff(delay 3000))public String createOrder(CreateOrderReq createOrderReq) {log.info(createOrder.req createOrderReq:{}, JSON.toJSONString(createOrderReq));try {log.info(createOrder.deductStock.调用时间{}, DateUtil.formatDateTime(DateUtil.date()));// 扣减库存服务this.deductStock(createOrderReq);} catch (Exception e) {throw new RuntimeException(e);}return UUID.randomUUID().toString();}/*** 模拟扣减库存*/private void deductStock(CreateOrderReq createOrderReq) {throw new RuntimeException(库存扣减失败);}/*** 当重试四次仍未能成功创建订单时调用此方法进行最终处理** param ex 异常对象包含重试失败的原因* param createOrderReq 创建订单的请求对象包含订单相关信息* return 返回处理结果此处返回fail表示最终失败*/Recoverpublic String recover(Exception ex, CreateOrderReq createOrderReq) {// 记录重试四次后仍失败的日志包括异常信息和订单请求内容log.error(recover.resp.重试四次还是失败.error:{},createOrderReq:{},ex.getMessage(),JSON.toJSONString(createOrderReq));// 处理最终失败的情况// 可以记录日志,或者是投递MQ,采用最终一致性的方式处理return fail;}
}
4. 启用重试功能
在主配置类或启动类上添加 EnableRetry 注解以启用重试功能
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.retry.annotation.EnableRetry;SpringBootApplication
EnableRetry
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}5. 验证重试方法 RestController
RequiredArgsConstructor
public class RetryController {private final OrderService orderService;GetMapping(getRetry)public String retry(){CreateOrderReq createOrderReq new CreateOrderReq();createOrderReq.setOrderId(UUID.randomUUID().toString());createOrderReq.setProductId(SKU001);createOrderReq.setCount(10);createOrderReq.setMoney(100);return orderService.createOrder(createOrderReq);}
}6.执行操作说明
添加依赖引入了 Spring Retry 和 AOP 的依赖以便使用重试功能。配置重试模板创建了一个配置类 CustomRetryConfig配置了重试策略设置最大重试次数为5次。创建服务类在 OrderServiceImpl 类中使用 Retryable 注解标记了 createOrder 方法指定了当发生 Exception 时进行重试最大重试次数为4次每次重试间隔3秒。同时使用 Recover 注解标记了 recover 方法当所有重试都失败后会调用这个方法。启用重试功能在主配置类或启动类上添加 EnableRetry 注解以启用重试功能。
Retry执行流程 Retry整体流程图 #mermaid-svg-ys1JFGtU0CRjE8Ro {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-ys1JFGtU0CRjE8Ro .error-icon{fill:#552222;}#mermaid-svg-ys1JFGtU0CRjE8Ro .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ys1JFGtU0CRjE8Ro .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-ys1JFGtU0CRjE8Ro .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ys1JFGtU0CRjE8Ro .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ys1JFGtU0CRjE8Ro .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ys1JFGtU0CRjE8Ro .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ys1JFGtU0CRjE8Ro .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ys1JFGtU0CRjE8Ro .marker.cross{stroke:#333333;}#mermaid-svg-ys1JFGtU0CRjE8Ro svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ys1JFGtU0CRjE8Ro .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ys1JFGtU0CRjE8Ro .cluster-label text{fill:#333;}#mermaid-svg-ys1JFGtU0CRjE8Ro .cluster-label span{color:#333;}#mermaid-svg-ys1JFGtU0CRjE8Ro .label text,#mermaid-svg-ys1JFGtU0CRjE8Ro span{fill:#333;color:#333;}#mermaid-svg-ys1JFGtU0CRjE8Ro .node rect,#mermaid-svg-ys1JFGtU0CRjE8Ro .node circle,#mermaid-svg-ys1JFGtU0CRjE8Ro .node ellipse,#mermaid-svg-ys1JFGtU0CRjE8Ro .node polygon,#mermaid-svg-ys1JFGtU0CRjE8Ro .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ys1JFGtU0CRjE8Ro .node .label{text-align:center;}#mermaid-svg-ys1JFGtU0CRjE8Ro .node.clickable{cursor:pointer;}#mermaid-svg-ys1JFGtU0CRjE8Ro .arrowheadPath{fill:#333333;}#mermaid-svg-ys1JFGtU0CRjE8Ro .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ys1JFGtU0CRjE8Ro .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ys1JFGtU0CRjE8Ro .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-ys1JFGtU0CRjE8Ro .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-ys1JFGtU0CRjE8Ro .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ys1JFGtU0CRjE8Ro .cluster text{fill:#333;}#mermaid-svg-ys1JFGtU0CRjE8Ro .cluster span{color:#333;}#mermaid-svg-ys1JFGtU0CRjE8Ro div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ys1JFGtU0CRjE8Ro :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是 否 是 否 开始操作 操作成功? 结束 等待延迟 递增重试计数 达到最大重试次数? 抛出异常 重试操作 打印日志
从日志分析每隔3秒钟会重试一次直到到达设置最大重试次数会调用功font stylecolor:#DF2A3F;recover/font方法中 7.结论
通过以上步骤我们成功地在 Spring Boot应用中集成了 Spring Retry 技术实现了服务调用的重试机制。这不仅提高了系统的健壮性和稳定性还减少了因网络问题或外部服务暂时不可用导致的请求失败。希望本文对你理解和应用 Spring Boot 中的重试技术有所帮助。 Retry配置的优先级规则
方法级别配置如果某个配置在方法上定义了则该方法上的配置会覆盖类级别的配置和全局配置。类级别配置如果某个配置在类上定义了并且该类的方法没有单独定义配置则使用类级别的配置。全局配置如果没有在方法或类上定义配置则使用全局配置。
下面通过一个具体的例子来展示这些优先级规则。假设我们有一个服务类 font stylecolor:rgb(44, 44, 54);MyService/font其中包含一些方法并且我们在不同的层次上进行了重试策略的配置。
示例代码
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;Service
Retryable(value {RuntimeException.class},maxAttempts 3,backoff Backoff(delay 1000) // 类级别的配置
)
public class MyService {Retryable(value {RuntimeException.class},maxAttempts 5,backoff Backoff(delay 500) // 方法级别的配置)public void retryableMethodWithSpecificConfig() {System.out.println(Retrying with specific config...);throw new RuntimeException(Simulated exception);}Retryable(value {RuntimeException.class})public void retryableMethodWithoutSpecificDelay() {System.out.println(Retrying without specific delay...);throw new RuntimeException(Simulated exception);}public void nonRetryableMethod() {System.out.println(This method does not retry.);throw new RuntimeException(Simulated exception);}
}解释
retryableMethodWithSpecificConfig 方法级别配置 font stylecolor:rgb(44, 44, 54);maxAttempts 5/fontfont stylecolor:rgb(44, 44, 54);backoff.delay 500/font 这些配置会覆盖类级别的配置。 retryableMethodWithoutSpecificDelay 方法级别配置 font stylecolor:rgb(44, 44, 54);maxAttempts 3/font 继承自类级别font stylecolor:rgb(44, 44, 54);backoff.delay 1000/font 继承自类级别 这些配置继承自类级别的配置。 nonRetryableMethod 该方法没有使用 font stylecolor:rgb(44, 44, 54);Retryable/font 注解因此不会进行重试。
全局配置示例
为了进一步说明全局配置的优先级我们可以配置一个全局的重试模板。
配置类 import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;Configuration
public class RetryConfig {Beanpublic RetryTemplate retryTemplate() {RetryTemplate template new RetryTemplate();SimpleRetryPolicy policy new SimpleRetryPolicy();policy.setMaxAttempts(4);template.setRetryPolicy(policy);FixedBackOffPolicy fixedBackOffPolicy new FixedBackOffPolicy();fixedBackOffPolicy.setBackOffPeriod(750L);template.setBackOffPolicy(fixedBackOffPolicy);return template;}
}使用全局配置的服务类 import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;Service
public class AnotherService {Autowiredprivate RetryTemplate retryTemplate;Retryable(value {RuntimeException.class},maxAttempts 6, // 方法级别的配置backoff Backoff(delay 300) // 方法级别的配置)public void retryableMethodWithGlobalAndLocalConfig() {System.out.println(Retrying with global and local config...);throw new RuntimeException(Simulated exception);}Retryable(value {RuntimeException.class})public void retryableMethodWithOnlyGlobalConfig() {System.out.println(Retrying with only global config...);throw new RuntimeException(Simulated exception);}
}解释
retryableMethodWithGlobalAndLocalConfig 方法级别配置 font stylecolor:rgb(44, 44, 54);maxAttempts 6/fontfont stylecolor:rgb(44, 44, 54);backoff.delay 300/font 这些配置会覆盖全局配置。 retryableMethodWithOnlyGlobalConfig 全局配置 font stylecolor:rgb(44, 44, 54);maxAttempts 4/fontfont stylecolor:rgb(44, 44, 54);backoff.delay 750/font 这些配置继承自全局配置。
总结一下配置的优先级从高到低依次是
方法级别配置类级别配置全局配置
希望这个示例能帮助你理解不同层次配置的优先级。 什么样的场景不适合Retry 在使用重试机制时确实有一些场景不适合应用重试策略。了解这些场景有助于避免不必要的重试操作从而提高系统的性能和稳定性。以下是几种不适合使用重试机制的常见场景 幂等性不可保证的操作 解释如果一个操作不是幂等的即多次执行会产生不同的结果那么重试可能导致数据不一致或其他问题。示例插入数据库记录的操作通常不是幂等的因为重复插入会导致重复的数据。 长时间运行的操作 解释对于耗时较长的操作频繁重试可能会导致系统资源被大量占用影响其他任务的执行。示例批量处理大数据集、长时间计算的任务。 外部依赖不稳定但无法恢复 解释某些外部服务或API可能存在根本性的故障无法通过简单的重试解决。在这种情况下重试只会浪费资源。示例调用第三方支付接口如果返回的是明确的失败状态码如账户余额不足则不应该重试。 网络超时且无可用备用路径 解释在网络请求超时时如果没有任何备用路径或解决方案重试可能仍然会失败。示例尝试连接到某个特定IP地址的服务如果该地址一直不通则重试没有意义。 用户交互过程中需要立即反馈的操作 解释在用户等待响应的过程中长时间的重试可能导致用户体验不佳。示例提交表单后立即显示成功消息如果在此期间发生错误并进行重试用户可能会感到困惑。 涉及敏感信息的操作 解释对于涉及敏感信息的操作如密码修改、资金转账重试可能会导致敏感信息泄露或重复操作。示例更新用户的银行账户信息一旦确认操作完成不应再进行重试。 事务边界内的操作 解释在事务边界内重试可能会导致事务冲突或回滚增加复杂性。示例在一个复杂的数据库事务中部分操作失败后进行重试可能导致整个事务失败。 已知的永久性错误 解释如果能够明确判断出错误是永久性的如配置错误、代码bug重试不会解决问题。示例尝试读取不存在的文件这种错误通常是永久性的。 高并发环境下的写操作 解释在高并发环境下频繁的重试可能会加剧数据库负载导致更多的锁竞争和死锁。示例在电商网站的下单高峰期对库存的减少操作不宜频繁重试。
示例代码
为了更好地理解这些原则下面是一个简单的示例展示了如何在Spring Boot中使用font stylecolor:rgb(44, 44, 54);Retryable/font注解并根据上述原则决定哪些操作适合重试。 import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;Service
public class MyService {// 适合重试的操作幂等性强、短时间操作、可恢复的错误Retryable(value {RuntimeException.class},maxAttempts 3,backoff Backoff(delay 1000))public void retryableFetchData() {System.out.println(Fetching data...);// 模拟网络请求或短暂的外部服务调用if (Math.random() 0.5) {throw new RuntimeException(Simulated transient network error);}}// 不适合重试的操作幂等性不可保证、长时间运行public void nonRetryableLongRunningTask() {System.out.println(Starting long-running task...);try {Thread.sleep(10000); // 模拟长时间运行的任务} catch (InterruptedException e) {Thread.currentThread().interrupt();}System.out.println(Long-running task completed.);}// 不适合重试的操作涉及敏感信息public void updateSensitiveInformation(String sensitiveData) {System.out.println(Updating sensitive information...);// 这里假设更新操作不是幂等的也不应该重试throw new RuntimeException(Simulated failure in updating sensitive information);}// 不适合重试的操作已知的永久性错误public void fetchDataFromNonExistentResource() {System.out.println(Fetching data from a non-existent resource...);throw new RuntimeException(Permanent error: Resource not found);}
}解释
retryableFetchData 适用条件 幂等性强每次请求的结果相同。短时间操作模拟网络请求或短暂的外部服务调用。可恢复的错误模拟暂时的网络错误。 重试策略 最大重试次数为3次。每次重试间隔1秒。 nonRetryableLongRunningTask 不适用原因 长时间运行模拟长时间运行的任务。重试可能导致资源过度消耗。 updateSensitiveInformation 不适用原因 涉及敏感信息更新操作不是幂等的也不应该重试。重试可能导致数据不一致或其他安全问题。 fetchDataFromNonExistentResource 不适用原因 已知的永久性错误资源不存在重试不会解决问题。
通过这些示例你可以更好地理解哪些操作适合重试以及为什么某些操作不适合重试。