SpringBoot4 引入了内置的容错功能,通过 @Retryable 和 @ConcurrencyLimit 注解,无需再单独引入 spring-retry 模块即可实现重试和并发控制能力。
分布式应用程序复杂性不断增加,系统的健壮性和可靠性成为了开发者面临的重要挑战,网络波动、外部服务暂时不可用、数据库连接超时等问题在生产环境中屡见不鲜。为了应对这些挑战,开发者通常需要实现重试机制(Retry Mechanism)、并发控制(Concurrency Control)、熔断降级(Circuit Breaker)等容错设计来提高系统的容错能力。提供系统的健壮性。
SpringBoot4 基于Spring Framework7.0 。 通过声明式注解配置即可实现复杂的重试策略,无需手写繁琐的处理逻辑,代码简洁优雅且功能强大。
主要新增的注解包括:
@Retryable:方法重试功能(Method Retry)@ConcurrencyLimit:并发限制功能(Concurrency Throttling)
2.1 默认重试
@Retryable,它指定了单个方法(在方法级别声明注释)或给定类中所有代理调用方法(在类级别声明注解的)的重试特性。
示例代码:
@Retryable
public String queryOrder() {
System.out.println(">>>>>>>查询订单queryOrder()方法执行次数:" + (index++) );
String orderUrl = "http://localhost:8080/api/orders?orderId=A2025001";
RestTemplate restTemplate = new RestTemplate();
Order order = restTemplate.getForObject(orderUrl,Order.class);
return "HTTP服务查询订单:" + order;
}
默认重试策略:
- 对于抛出的任何异常都执行重试
- 在初始失败后最多重试3次(重试第1次 + 3次重试)
- 两次尝试之间延迟1秒(固定间隔时间)
完整实现:
1.创建提供者Web应用( boot-04-retry ),接口功能查询订单
创建基于Spring Boot 4的Web应用, 订单类定义如下:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
private String id;
private String name;
}
订单查询接口
@RestController
public class OrderController {
@GetMapping(value = "/api/orders")
public ResponseEntity<Order> queryOrderById(String orderId){
Order order = new Order(orderId,"杭州3日自由行");
try{
Thread.sleep(500);
throw new RuntimeException("查询异常");
}catch (Exception e){
e.printStackTrace();
return ResponseEntity.internalServerError().body(order);
}
}
}
订单查询功能,500毫秒后抛出异常。
2.调用服务
创建基于Spring Boot4 Web应用( boot-04-client )。 依赖只需要spring-boot-starter-web ,不需要spring-retry
作为接口的订单类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
private String id;
private String name;
}
订单查询服务类
@Service
public class QueryOrderService {
private int index = 1;
@Retryable
public String queryOrder() {
System.out.println(">>>>>>>查询订单queryOrder()方法执行次数:" + (index++) );
String orderUrl = "http://localhost:8080/api/orders?orderId=A2025001";
RestTemplate restTemplate = new RestTemplate();
Order order = restTemplate.getForObject(orderUrl,Order.class);
return "HTTP服务查询订单:" + order;
}
}
查询入口Controller
@RestController
public class ConsumerClient {
@Resource
private QueryOrderService queryOrderService;
@GetMapping("/client")
public String queryOrder()
{
return queryOrderService.queryOrder();
}
}
配置声明 @EnableResilientMethods
@EnableResilientMethods
@SpringBootApplication
public class Boot04ClientApplication {
public static void main(String[] args) {
SpringApplication.run(Boot04ClientApplication.class, args);
}
}
3.测试重试
启动提供者boot-04-retry
启动客户端服务boot-04-client
Postman发送GET http://localhost:8000/client
查看两个应用的控制台
客户端服务boot-04-client控制台:
>>>>>>>查询订单queryOrder()方法执行次数:1
>>>>>>>查询订单queryOrder()方法执行次数:2
>>>>>>>查询订单queryOrder()方法执行次数:3
>>>>>>>查询订单queryOrder()方法执行次数:4
动提供者控制台
java.lang.RuntimeException: 查询异常
at com.bjpowernode.controller.OrderController.queryOrderById(OrderController.java:17)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:256)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:188)
...
java.lang.RuntimeException: 查询异常
at com.bjpowernode.controller.OrderController.queryOrderById(OrderController.java:17)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:256)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:188)
...
java.lang.RuntimeException: 查询异常
at com.bjpowernode.controller.OrderController.queryOrderById(OrderController.java:17)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:256)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:188)
...
java.lang.RuntimeException: 查询异常
at com.bjpowernode.controller.OrderController.queryOrderById(OrderController.java:17)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:256)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:188)
...
2.2 指定重试的异常类型
对特定的异常应用重试, 而不是所有异常
@Retryable( { NullPointerException.class} )
public String queryOrder() {
.......
}
2.3 重试策略配置
修改重试方法
@Service
public class QueryOrderService {
private int index = 1;
private long pre =0;
private long cur = 0;
@Retryable( maxAttempts = 5, // 最大重试次数
delay = 100, // 初始延迟(毫秒)
jitter = 10, // 随机抖动(Jitter),避免重试风暴
multiplier = 2, // 指数退避倍数
maxDelay = 1000 // 最大延迟上限
)
public String queryOrder() {
System.out.println( System.currentTimeMillis() + ">>>>>>>查询订单queryOrder()方法执行次数:" + (index++) );
cur = System.currentTimeMillis();
if( pre == 0 ){
pre = cur;
} else {
long diff = cur - pre - 1000;
System.out.println("diff = " + diff);
pre = cur;
}
try {
TimeUnit.MILLISECONDS.sleep(1000);
int x = 1/0;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "HTTP服务查询订单:";
}
}
重试策略:
maxAttempts = 5:最大重试次数为5次, 第一次请求后, 最多重试5次 delay = 100:初始延迟时间为100毫秒,第一次重试会在100ms后进行 jitter = 10:随机抖动值为10毫秒,实际延迟会在基础延迟上增加一个随机值(通常在±10ms范围内) multiplier = 2:指数退避倍数为2,每次重试的延迟时间会翻倍 maxDelay = 1000:最大延迟上限为1000毫秒(1秒),即使指数退避计算出更长的延迟时间,也不会超过这个值
延迟时间计算示例 基于这些参数,重试的延迟时间大致如下: 第1次重试:~100ms 第2次重试:~200ms 第3次重试:~400ms 第4次重试:~800ms 第5次重试:~1000ms(不超过最大延迟1000ms)
这种配置可以有效避免系统过载,同时通过指数退避和随机抖动来分散重试请求,防止重试风暴。
执行重试方法
1757753468129>>>>>>>查询订单queryOrder()方法执行次数:1
1757753469242>>>>>>>查询订单queryOrder()方法执行次数:2
diff = 108
1757753470432>>>>>>>查询订单queryOrder()方法执行次数:3
diff = 190
1757753471862>>>>>>>查询订单queryOrder()方法执行次数:4
diff = 430
1757753473737>>>>>>>查询订单queryOrder()方法执行次数:5
diff = 875
1757753475721>>>>>>>查询订单queryOrder()方法执行次数:6
diff = 984
2.4 @ConcurrencyLimit 并发控制
@ConcurrencyLimit 注解用于实现并发限流,限制方法的并发访问数量。 类级别和方法级别可用。
默认并发限制为 1 。
限制访问的方法
@RestController
public class LimitController {
//至多10个并发请求 @ConcurrencyLimit(value = 1)
@ConcurrencyLimit(value = 1)
@GetMapping("/hello")
public String hello(){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "hello world";
}
}
设置限制为 1 实现对方法的独占访问, 确保线程安全 , 方法调用为串行执行,
配置类
@EnableResilientMethods
@SpringBootApplication
public class Boot05ConcurrencyLimitApplication {
public static void main(String[] args) {
SpringApplication.run(Boot05ConcurrencyLimitApplication.class, args);
}
}
单元测试方法
@SpringBootTest
class Boot05ConcurrencyLimitApplicationTests {
private RestTemplate restTemplate = new RestTemplate();
@Test
void testConcurrencyTest() {
ExecutorService executorService = Executors.newFixedThreadPool(15);
List<Future<ResponseEntity<String>>> futures = new ArrayList<>();
for (int i = 0; i < 20; i++) {
Future<ResponseEntity<String>> future = executorService.submit(() -> {
return restTemplate.getForEntity("http://localhost:8080/hello", String.class);
});
futures.add(future);
}
int successCount = 0;
int failureCount = 0;
for (Future<ResponseEntity<String>> future : futures) {
try {
ResponseEntity<String> response = future.get(200, TimeUnit.MILLISECONDS);
if (response.getStatusCode().is2xxSuccessful()) {
successCount++;
} else {
System.out.println("失败:" + response.getStatusCode());
failureCount++;
}
} catch (Exception e) {
e.printStackTrace();
failureCount++;
future.cancel(true);
}
}
executorService.shutdown();
System.out.println("成功:" + successCount);
System.out.println("失败:" + failureCount);
}
}
访问超时200描述, 设置并发访问1 (尝试修改其他,比如10), 单元测试部分请求成功,部分失败。异常为超时错误。
同步调用拦截器: ConcurrencyThrottleInterceptor
异步调用:SimpleAsyncTaskExecutor