防止外部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)提供了系统健康状况和断路器状态的实时监控,方便运维人员及时发现问题。

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

相关推荐
Asthenia041211 分钟前
用RocketMQ和MyBatis实现下单-减库存-扣钱的事务一致性
后端
Pasregret24 分钟前
04-深入解析 Spring 事务管理原理及源码
java·数据库·后端·spring·oracle
Micro麦可乐25 分钟前
最新Spring Security实战教程(七)方法级安全控制@PreAuthorize注解的灵活运用
java·spring boot·后端·spring·intellij-idea·spring security
returnShitBoy33 分钟前
Go语言中的defer关键字有什么作用?
开发语言·后端·golang
Asthenia04121 小时前
面试场景题:基于Redisson、RocketMQ和MyBatis的定时短信发送实现
后端
教练 我想学编程1 小时前
学习记录706@微信小程序+springboot项目 真机测试 WebSocket错误: {errMsg: Invalid HTTP status.}连接不上
spring boot·学习·微信小程序
Asthenia04121 小时前
链路追踪视角:MyBatis-Plus 如何基于 MyBatis 封装 BaseMapper
后端
Ai 编码助手1 小时前
基于 Swoole 的高性能 RPC 解决方案
后端·rpc·swoole
翻滚吧键盘1 小时前
spring打包,打包错误
java·后端·spring
_Djhhh1 小时前
基于SpringAOP面向切面编程的一些实践(日志记录、权限控制、统一异常处理)
java·spring boot·spring·maven·sprint