防止外部API服务不可用拖垮系统的解决方案
在微服务架构中,系统间的API调用是常见场景。当外部API不可用时,如果没有适当的防护措施,可能会导致线程池耗尽、系统响应缓慢甚至整体崩溃。以下是一个基于Spring Boot的解决方案。
实际场景案例
假设我们有一个电商系统,需要调用外部支付服务API来处理订单支付。如果支付服务暂时不可用,我们不希望影响整个订单系统的运行。
解决方案核心技术
我们将使用以下技术来解决这个问题:
- 断路器模式(Circuit Breaker) - 使用Resilience4j实现
- 超时控制 - 避免长时间等待
- 线程隔离 - 防止API调用阻塞主服务
- 优雅降级 - 当服务不可用时提供备选方案
- 限流机制 - 控制请求速率
代码实现
1. 添加依赖(pom.xml)
xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
2. 配置文件(application.yml)
yaml
server:
port: 8080
spring:
application:
name: order-service
resilience4j:
circuitbreaker:
instances:
paymentService:
registerHealthIndicator: true
slidingWindowSize: 10
minimumNumberOfCalls: 5
permittedNumberOfCallsInHalfOpenState: 3
automaticTransitionFromOpenToHalfOpenEnabled: true
waitDurationInOpenState: 5s
failureRateThreshold: 50
eventConsumerBufferSize: 10
timeout:
instances:
paymentService:
timeoutDuration: 2s
ratelimiter:
instances:
paymentService:
limitForPeriod: 10
limitRefreshPeriod: 1s
timeoutDuration: 3s
retry:
instances:
paymentService:
maxAttempts: 3
waitDuration: 1s
enableExponentialBackoff: true
exponentialBackoffMultiplier: 2
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
3. 支付服务接口及模拟实现
java
package com.example.orderservice.service;
import lombok.Data;
public interface PaymentService {
PaymentResponse processPayment(PaymentRequest request);
}
@Data
class PaymentRequest {
private String orderId;
private double amount;
private String paymentMethod;
}
@Data
class PaymentResponse {
private String transactionId;
private String status;
private String message;
}
package com.example.orderservice.service.impl;
import com.example.orderservice.service.PaymentService;
import org.springframework.stereotype.Service;
import java.util.Random;
import java.util.UUID;
@Service
public class PaymentServiceImpl implements PaymentService {
private final Random random = new Random();
@Override
public PaymentResponse processPayment(PaymentRequest request) {
// 模拟外部API调用可能的延迟和失败
if (random.nextInt(10) < 3) {
// 30%概率服务超时
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
if (random.nextInt(10) < 2) {
// 20%概率服务失败
throw new RuntimeException("Payment service unavailable");
}
// 正常响应
PaymentResponse response = new PaymentResponse();
response.setTransactionId(UUID.randomUUID().toString());
response.setStatus("SUCCESS");
response.setMessage("Payment processed successfully");
return response;
}
}
4. 支付服务适配器(使用断路器等保护机制)
java
package com.example.orderservice.adapter;
import com.example.orderservice.service.PaymentService;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.ratelimiter.annotation.RateLimiter;
import io.github.resilience4j.retry.annotation.Retry;
import io.github.resilience4j.timelimiter.annotation.TimeLimiter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeoutException;
@Slf4j
@Component
@RequiredArgsConstructor
public class PaymentServiceAdapter {
private final PaymentService paymentService;
@CircuitBreaker(name = "paymentService", fallbackMethod = "fallbackProcessPayment")
@TimeLimiter(name = "paymentService")
@Retry(name = "paymentService")
@RateLimiter(name = "paymentService")
public CompletableFuture<PaymentResponse> processPayment(PaymentRequest request) {
return CompletableFuture.supplyAsync(() -> {
log.info("Calling payment service for order: {}", request.getOrderId());
return paymentService.processPayment(request);
});
}
public CompletableFuture<PaymentResponse> fallbackProcessPayment(PaymentRequest request, Exception ex) {
log.error("Payment service failed: {}", ex.getMessage());
PaymentResponse fallbackResponse = new PaymentResponse();
if (ex instanceof TimeoutException) {
log.warn("Payment service timeout for order: {}", request.getOrderId());
fallbackResponse.setStatus("PENDING");
fallbackResponse.setMessage("Payment is being processed, but status is unknown");
} else {
log.warn("Payment service unavailable for order: {}", request.getOrderId());
fallbackResponse.setStatus("QUEUED");
fallbackResponse.setMessage("Payment queued for later processing");
}
fallbackResponse.setTransactionId(UUID.randomUUID().toString() + "-fallback");
return CompletableFuture.completedFuture(fallbackResponse);
}
}
5. 订单服务
java
package com.example.orderservice.service;
import com.example.orderservice.adapter.PaymentServiceAdapter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderService {
private final PaymentServiceAdapter paymentAdapter;
public OrderResponse createOrder(OrderRequest request) {
// 创建订单逻辑...
String orderId = UUID.randomUUID().toString();
log.info("Order created: {}", orderId);
// 处理支付
PaymentRequest paymentRequest = new PaymentRequest();
paymentRequest.setOrderId(orderId);
paymentRequest.setAmount(request.getTotalAmount());
paymentRequest.setPaymentMethod(request.getPaymentMethod());
PaymentResponse paymentResponse;
try {
// 同步获取异步结果,但有超时保护
paymentResponse = paymentAdapter.processPayment(paymentRequest)
.get(3, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
log.error("Error processing payment", e);
paymentResponse = new PaymentResponse();
paymentResponse.setStatus("ERROR");
paymentResponse.setMessage("Payment processing failed");
}
// 组装订单响应
OrderResponse response = new OrderResponse();
response.setOrderId(orderId);
response.setStatus("CREATED");
response.setPaymentStatus(paymentResponse.getStatus());
response.setPaymentMessage(paymentResponse.getMessage());
return response;
}
}
@Data
class OrderRequest {
private double totalAmount;
private String paymentMethod;
private List<OrderItem> items;
}
@Data
class OrderItem {
private String productId;
private int quantity;
private double price;
}
@Data
class OrderResponse {
private String orderId;
private String status;
private String paymentStatus;
private String paymentMessage;
}
6. REST控制器
java
package com.example.orderservice.controller;
import com.example.orderservice.service.OrderService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/orders")
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
@PostMapping
public ResponseEntity<OrderResponse> createOrder(@RequestBody OrderRequest request) {
OrderResponse response = orderService.createOrder(request);
return ResponseEntity.ok(response);
}
}
解决方案说明
- 断路器模式:当支付服务连续失败达到阈值时,断路器打开,直接调用降级方法而不尝试调用实际服务
- 超时控制:设置2秒超时,避免长时间等待
- 线程隔离:使用CompletableFuture异步处理支付请求
- 优雅降级:提供fallback方法在服务不可用时返回备选结果
- 限流保护:限制每秒最多10个请求发送到支付服务
这种设计确保了即使支付API完全不可用,订单系统仍能继续工作,只是将支付请求标记为"待处理"或"已排队",可以在稍后重试。
监控端点(actuator)提供了系统健康状况和断路器状态的实时监控,方便运维人员及时发现问题。
需要根据实际业务调整配置参数,如超时时间、重试次数、限流阈值等。