防止外部API服务不可用拖垮系统的解决方案

防止外部API服务不可用拖垮系统的解决方案

在微服务架构中,系统间的API调用是常见场景。当外部API不可用时,如果没有适当的防护措施,可能会导致线程池耗尽、系统响应缓慢甚至整体崩溃。以下是一个基于Spring Boot的解决方案。

实际场景案例

假设我们有一个电商系统,需要调用外部支付服务API来处理订单支付。如果支付服务暂时不可用,我们不希望影响整个订单系统的运行。

解决方案核心技术

我们将使用以下技术来解决这个问题:

  1. 断路器模式(Circuit Breaker) - 使用Resilience4j实现
  2. 超时控制 - 避免长时间等待
  3. 线程隔离 - 防止API调用阻塞主服务
  4. 优雅降级 - 当服务不可用时提供备选方案
  5. 限流机制 - 控制请求速率

代码实现

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);
    }
}

解决方案说明

  1. 断路器模式:当支付服务连续失败达到阈值时,断路器打开,直接调用降级方法而不尝试调用实际服务
  2. 超时控制:设置2秒超时,避免长时间等待
  3. 线程隔离:使用CompletableFuture异步处理支付请求
  4. 优雅降级:提供fallback方法在服务不可用时返回备选结果
  5. 限流保护:限制每秒最多10个请求发送到支付服务

这种设计确保了即使支付API完全不可用,订单系统仍能继续工作,只是将支付请求标记为"待处理"或"已排队",可以在稍后重试。

监控端点(actuator)提供了系统健康状况和断路器状态的实时监控,方便运维人员及时发现问题。

需要根据实际业务调整配置参数,如超时时间、重试次数、限流阈值等。

相关推荐
Java中文社群3 分钟前
Dify实战案例:MySQL查询助手!嘎嘎好用
java·人工智能·后端
bobz96524 分钟前
源码编译 libvirt
后端
道友小小怪25 分钟前
Spring Bean的生命周期及常见问题
后端·spring·面试
Java微观世界28 分钟前
让你秒懂字符集与编码:详解ASCII、GB2312、Unicode和UTF-8
后端
sszdzq33 分钟前
SpringCloudGateway 自定义局部过滤器
java·spring boot
aiopencode1 小时前
WebDebugX 如何助力跨平台 WebView 页面调试?开发者实战拆解
后端
大麦若叶茶1 小时前
每天学习一点点之进程与线程、并行与并发
后端
咸虾米1 小时前
在uniCloud云对象内使用unipay的微信退款出现错误“uniPayCo.refund Error: token校验未通过”的解决方案
前端·后端
云胡1 小时前
Mybatis Plus 自定义 SQL
后端
37手游后端团队1 小时前
15页PPT深挖AI编程新范式!程序员必看的破局指南
程序员·ai编程·求职