第五章:Resilience4j熔断机制

理论讲解

什么是熔断机制?
熔断机制就像是家里的电路保险丝,当电流过大(服务调用异常过多)时,保险丝会自动熔断(打开熔断器),防止电器损坏(防止系统崩溃)。

过一段时间后,保险丝会恢复(半开状态),如果电流正常(服务调用成功)就完全恢复(关闭熔断器),否则再次熔断。

应用场景举例:
想象一下电商系统的支付服务:

  • 正常情况下,支付服务快速响应
  • 如果支付服务出现故障,调用支付服务的订单服务会不断重试,导致资源耗尽
  • 使用熔断器,当失败次数达到阈值时,熔断器打开,直接返回失败,不再调用支付服务
  • 过一段时间后,熔断器进入半开状态,允许部分请求通过测试支付服务是否恢复
  • 如果测试请求成功,熔断器关闭,恢复正常调用

项目结构

chapter-05-resilience4j/
├── commons-service/        # 公共基础服务
├── eureka-server/           # 注册中心
├── user-service/           # 用户服务
├── product-service/        # 商品服务
├── order-service/          # 订单服务(使用Resilience4j)
└── payment-service/        # 支付服务(模拟故障)

完整代码实现

1. 父工程pom.xml更新

在父工程中添加Resilience4j依赖管理:


2. Order Service订单服务(使用Resilience4j)

pom.xml更新
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.lihaozhe</groupId>
    <artifactId>springcloud-course</artifactId>
    <version>1.0.0</version>
    <relativePath>../../pom.xml</relativePath>
  </parent>

  <artifactId>order-service</artifactId>

  <properties>
    <maven.compiler.source>25</maven.compiler.source>
    <maven.compiler.target>25</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <!-- 替换为你的启动类全路径 -->
    <start-class>com.lihaozhe.orderservice.OrderServiceApplication</start-class>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!-- 引出 commons-service 公共基础服务 -->
    <dependency>
      <groupId>com.lihaozhe</groupId>
      <artifactId>commons-service</artifactId>
      <version>1.0.0</version>
    </dependency>

    <!-- Spring Cloud loadbalancer依赖管理 -->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>

    <!-- Spring Cloud openfeign 依赖管理 -->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>

    <!-- Apache HttpClient 5 -->
    <dependency>
      <groupId>org.apache.httpcomponents.client5</groupId>
      <artifactId>httpclient5</artifactId>
    </dependency>

    <!-- 可选:如果需要连接池等高级功能 -->
    <dependency>
      <groupId>org.apache.httpcomponents.core5</groupId>
      <artifactId>httpcore5</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    <!-- Resilience4j 核心依赖 -->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
    </dependency>

    <!-- Resilience4j 依赖 -->
    <dependency>
      <groupId>io.github.resilience4j</groupId>
      <artifactId>resilience4j-spring-boot3</artifactId>
    </dependency>
    <dependency>
      <groupId>io.github.resilience4j</groupId>
      <artifactId>resilience4j-circuitbreaker</artifactId>
    </dependency>
    <dependency>
      <groupId>io.github.resilience4j</groupId>
      <artifactId>resilience4j-ratelimiter</artifactId>
    </dependency>
    <dependency>
      <groupId>io.github.resilience4j</groupId>
      <artifactId>resilience4j-retry</artifactId>
    </dependency>
    <dependency>
      <groupId>io.github.resilience4j</groupId>
      <artifactId>resilience4j-bulkhead</artifactId>
    </dependency>
    <dependency>
      <groupId>io.github.resilience4j</groupId>
      <artifactId>resilience4j-cache</artifactId>
    </dependency>
    <dependency>
      <groupId>io.github.resilience4j</groupId>
      <artifactId>resilience4j-timelimiter</artifactId>
    </dependency>

    <!-- Resilience4j Actuator 集成,用于监控 -->
    <dependency>
      <groupId>io.github.resilience4j</groupId>
      <artifactId>resilience4j-micrometer</artifactId>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
          <!-- 替换为你的启动类全路径 -->
          <mainClass>com.lihaozhe.orderservice.OrderServiceApplication</mainClass>
        </configuration>
        <!-- 可选:如果需要打包为可执行jar,添加此配置 -->
        <executions>
          <execution>
            <goals>
              <goal>repackage</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

</project>
应用启动类更新
package com.lihaozhe.orderservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
 * 订单服务应用启动类 - Resilience4j版本
 * 使用Resilience4j提供熔断、限流、重试等容错能力
 */
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OrderServiceApplication {
  public static void main(String[] args) {
    SpringApplication.run(OrderServiceApplication.class, args);
  }
}
Resilience4j配置
全局配置
package com.lihaozhe.orderservice.config;

import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.timelimiter.TimeLimiterConfig;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JCircuitBreakerFactory;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JConfigBuilder;
import org.springframework.cloud.client.circuitbreaker.Customizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.Duration;

/**
 * Resilience4j配置类
 * 配置熔断器、时间限制器等
 *
 * @author 李昊哲
 * @version 1.0.0
 */
@Configuration
public class Resilience4jConfig {
  /**
   * 全局熔断器配置
   */
  @Bean
  public Customizer<Resilience4JCircuitBreakerFactory> globalCircuitBreakerConfig() {
    CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
        // 失败率阈值,超过50%失败则打开熔断器
        .failureRateThreshold(50)
        // 熔断器打开后等待时间,5秒后进入半开状态
        .waitDurationInOpenState(Duration.ofSeconds(5))
        // 熔断器半开状态下允许的调用次数
        .permittedNumberOfCallsInHalfOpenState(3)
        // 滑动窗口大小,基于最近10次调用来计算失败率
        .slidingWindowSize(10)
        // 记录异常类型,所有异常都视为失败
        .recordExceptions(Exception.class)
        .build();

    TimeLimiterConfig timeLimiterConfig = TimeLimiterConfig.custom()
        // 设置超时时间为3秒
        .timeoutDuration(Duration.ofSeconds(3))
        .build();

    return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
        .timeLimiterConfig(timeLimiterConfig)
        .circuitBreakerConfig(circuitBreakerConfig)
        .build());
  }

  /**
   * 支付服务专用的熔断器配置
   * 更严格的配置,因为支付服务是关键服务
   */
  @Bean
  public Customizer<Resilience4JCircuitBreakerFactory> paymentServiceCircuitBreakerConfig() {
    CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
        .failureRateThreshold(30)  // 30%失败率就打开熔断
        .waitDurationInOpenState(Duration.ofSeconds(10)) // 10秒后进入半开
        .permittedNumberOfCallsInHalfOpenState(2)
        .slidingWindowSize(20)
        .recordExceptions(Exception.class)
        .build();

    return factory -> factory.configure(builder -> builder
        .circuitBreakerConfig(circuitBreakerConfig)
        .timeLimiterConfig(TimeLimiterConfig.custom()
            .timeoutDuration(Duration.ofSeconds(5))
            .build()), "paymentService");
  }
}

应用配置
# application.properties
#=============================================
# 0. 必改/高危配置(上线前一定要检查)
#=============================================
# 订单服务实例端口,集群部署时切忌重复
server.port=8083

#=============================================
# 1. Spring 基础配置
#=============================================
# Eureka 注册名,一旦上线禁止变更(消费者会硬编码)
spring.application.name=order-service
spring.cloud.loadbalancer.enabled=true
spring.cloud.loadbalancer.cache.ttl=0
spring.cloud.openfeign.circuitbreaker.enabled=true
spring.cloud.openfeign.httpclient.hc5.enabled=true

#=============================================
# 2. Eureka 实例级配置
#=============================================
# 在 Eureka 控制台显示的主机名,可读性优先
eureka.instance.hostname=order-service
eureka.instance.prefer-ip-address=true
eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port}
eureka.instance.lease-renewal-interval-in-seconds=5
eureka.instance.lease-expiration-duration-in-seconds=10

#=============================================
# 3. Eureka 客户端行为配置
#=============================================
# 是否把自己注册到 Eureka(默认 true,显式写出可提醒运维)
eureka.client.register-with-eureka=true
# 是否拉取注册表(默认 true,同上)
eureka.client.fetch-registry=true
eureka.client.service-url.defaultZone=http://admin:lihaozhe@localhost:8761/eureka/

#=============================================
# 5. Resilience4j配置
#=============================================
resilience4j.circuitbreaker.configs.default.failure-rate-threshold=50
resilience4j.circuitbreaker.configs.default.wait-duration-in-open-state=5s
resilience4j.circuitbreaker.configs.default.permitted-number-of-calls-in-half-open-state=3
resilience4j.circuitbreaker.configs.default.sliding-window-size=10
resilience4j.circuitbreaker.configs.default.minimum-number-of-calls=5

resilience4j.circuitbreaker.instances.paymentService.failure-rate-threshold=30
resilience4j.circuitbreaker.instances.paymentService.wait-duration-in-open-state=10s
resilience4j.circuitbreaker.instances.paymentService.sliding-window-size=20
resilience4j.circuitbreaker.instances.paymentService.minimum-number-of-calls=10

resilience4j.timelimiter.configs.default.timeout-duration=3s

resilience4j.retry.configs.default.max-attempts=3
resilience4j.retry.configs.default.wait-duration=1s

#=============================================
# 5. Actuator 监控配置
#=============================================
# 按需暴露,生产勿暴露 env/beans
management.endpoints.web.exposure.include=health,info,metrics,circuitbreakers
management.endpoint.health.show-details=always

#=============================================
# 6. 日志级别
#=============================================
# 业务包日志级别,上线后改为 INFO 或 WARN
logging.level.com.lihaozhe.orderservice=DEBUG
logging.level.org.springframework.cloud.loadbalancer=DEBUG
logging.level.io.github.resilience4j=DEBUG

# application.yml
#=============================================
# 0. 必改/高危配置(上线前一定要检查)
#=============================================
# 订单服务实例端口,集群部署时切忌重复
server:
  port: 8083

#=============================================
# 1. Spring 基础配置
#=============================================
# Eureka 注册名,一旦上线禁止变更(消费者会硬编码)
spring:
  application:
    name: order-service
  cloud:
    loadbalancer:
      # 为所有服务启用 Spring Cloud LoadBalancer
      enabled: true
      # 启用自定义负载均衡配置
      # configurations: custom
      cache:
        # 禁用缓存,每次请求都从注册中心获取最新实例(生产环境不建议)
        ttl: 0
    openfeign:
      # 启用熔断器
      circuit breaker:
        enabled: true
      httpclient:
        # 使 Feign 能够使用 Apache HTTP 客户端 5
        hc5:
          enabled: true
#=============================================
# 2. Eureka 实例级配置
#=============================================
# 在 Eureka 控制台显示的主机名,可读性优先
eureka:
  instance:
    hostname: order-service
    # 注册时优先使用 IP,防止 hostname 解析失败
    prefer-ip-address: true
    # 唯一标识,格式 IP:端口
    instance-id: ${spring.cloud.client.ip-address}:${server.port}
    # 心跳间隔 5s,缩短感知时间
    lease-renewal-interval-in-seconds: 5
    # 10s 内收不到心跳即剔除,开发调试可设短
    lease-expiration-duration-in-seconds: 10

  #=============================================
  # 3. Eureka 客户端行为配置
  #=============================================
  # 是否把自己注册到 Eureka(默认 true,显式写出可提醒运维)
  client:
    register-with-eureka: true
    # 是否拉取注册表(默认 true,同上)
    fetch-registry: true
    service-url:
      # 注册中心地址(带安全认证)
      defaultZone: http://admin:lihaozhe@localhost:8761/eureka/

#=============================================
# 5. Resilience4j配置
#=============================================
resilience4j:
  circuitbreaker:
    configs:
      default:
        # 熔断器触发阈值,失败率达到50%时触发熔断
        failure-rate-threshold: 50
        # 熔断器开启状态持续时间,5秒后进入半开状态
        wait-duration-in-open-state: 5s
        # 半开状态下允许通过的请求数量
        permitted-number-of-calls-in-half-open-state: 3
        # 滑动窗口大小,用于计算失败率的请求数量
        sliding-window-size: 10
        # 最小请求数,达到此数量才开始计算失败率
        minimum-number-of-calls: 5
    instances:
      # 针对paymentService服务的特定配置
      paymentService:
        # 更严格的失败率阈值,30%失败率即触发熔断
        failure-rate-threshold: 30
        # 较长的熔断持续时间,10秒后进入半开状态
        wait-duration-in-open-state: 10s
        # 更大的滑动窗口,统计更多请求数据
        sliding-window-size: 20
        # 需要至少10个请求才计算失败率
        minimum-number-of-calls: 10
  timelimiter:
    configs:
      default:
        # 时间限制器超时时间,超过3秒的请求将被中断
        timeout-duration: 3s
  retry:
    configs:
      default:
        # 最大重试次数,总共尝试3次(包括初始请求)
        max-attempts: 3
        # 重试间隔时间,每次重试等待1秒
        wait-duration: 1s

#=============================================
# 5. Actuator 监控配置
#=============================================
# 按需暴露,生产勿暴露 env/beans
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,circuitbreakers
  endpoint:
    health:
      # 默认 never,always 方便排查
      show-details: always

#=============================================
# 6. 日志级别
#=============================================
# 业务包日志级别,上线后改为 INFO 或 WARN
logging:
  level:
    com.lihaozhe.orderservice: DEBUG
    org.springframework.cloud.loadbalancer: DEBUG
    io.github.resilience4j: DEBUG

使用Resilience4j的服务类
订单服务类(增强版)
package com.lihaozhe.orderservice.service;

import com.lihaozhe.orderservice.client.PaymentServiceClient;
import com.lihaozhe.orderservice.client.ProductServiceClient;
import com.lihaozhe.orderservice.client.UserServiceClient;
import com.lihaozhe.orderservice.dto.OrderDTO;
import com.lihaozhe.orderservice.dto.OrderItemDTO;
import com.lihaozhe.userservice.dto.UserDTO;
import com.lihaozhe.productservice.dto.ProductDTO;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.retry.annotation.Retry;
import io.github.resilience4j.timelimiter.annotation.TimeLimiter;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

/**
 * 订单服务类 - Resilience4j版本
 * 使用熔断器、重试、超时等容错机制
 *
 * @author 李昊哲
 * @version 1.0.0
 */
@Service
public class OrderService {

  private final UserServiceClient userServiceClient;
  private final ProductServiceClient productServiceClient;
  private final PaymentServiceClient paymentServiceClient;
  private final CircuitBreakerFactory circuitBreakerFactory;

  private final List<OrderDTO> orders = new ArrayList<>();
  private Long orderIdCounter = 1L;

  // 熔断器名称常量
  private static final String PAYMENT_SERVICE_CB = "paymentService";
  private static final String USER_SERVICE_CB = "userService";
  private static final String PRODUCT_SERVICE_CB = "productService";

  public OrderService(UserServiceClient userServiceClient,
                      ProductServiceClient productServiceClient,
                      PaymentServiceClient paymentServiceClient,
                      CircuitBreakerFactory circuitBreakerFactory) {
    this.userServiceClient = userServiceClient;
    this.productServiceClient = productServiceClient;
    this.paymentServiceClient = paymentServiceClient;
    this.circuitBreakerFactory = circuitBreakerFactory;
  }

  /**
   * 创建订单 - 使用熔断器保护
   * CircuitBreaker: 当支付服务失败率过高时,熔断器会打开,直接返回降级结果
   * Retry: 当支付服务调用失败时,自动重试3次
   * TimeLimiter: 设置支付调用的超时时间为5秒
   */
  @CircuitBreaker(name = PAYMENT_SERVICE_CB, fallbackMethod = "createOrderFallback")
  @Retry(name = PAYMENT_SERVICE_CB, fallbackMethod = "createOrderFallback")
  public Map<String, Object> createOrder(Map<String, Object> orderRequest) {
    Long userId = Long.valueOf(orderRequest.get("userId").toString());
    Long productId = Long.valueOf(orderRequest.get("productId").toString());
    Integer quantity = Integer.valueOf(orderRequest.get("quantity").toString());

    System.out.println("创建订单 - 用户ID: " + userId + ", 商品ID: " + productId);

    try {
      // 1. 调用用户服务
      UserDTO user = userServiceClient.getUserById(userId);
      if (user == null) {
        return Map.of("success", false, "message", "用户不存在");
      }

      // 2. 调用商品服务
      Map<String, Object> productResponse = productServiceClient.getProductById(productId);
      if (productResponse == null || productResponse.get("product") == null) {
        return Map.of("success", false, "message", "商品不存在");
      }

      // 3. 提取商品信息
      Map<String, Object> productMap = (Map<String, Object>) productResponse.get("product");
      ProductDTO product = mapToProductDTO(productMap);

      // 4. 检查库存
      if (product.getStock() < quantity) {
        return Map.of("success", false, "message", "库存不足");
      }

      // 5. 创建订单
      OrderItemDTO orderItem = new OrderItemDTO(productId, product.getName(), quantity, product.getPrice());
      List<OrderItemDTO> items = List.of(orderItem);
      Double totalAmount = product.getPrice() * quantity;

      OrderDTO order = new OrderDTO(orderIdCounter++, userId, items, totalAmount, "CREATED");
      orders.add(order);

      // 6. 更新库存
      Map<String, Integer> stockUpdate = Map.of("quantity", -quantity);
      productServiceClient.updateStock(productId, stockUpdate);

      // 7. 调用支付服务 - 这个调用受熔断器保护
      Map<String, Object> paymentResult = processPayment(order, user);

      // 8. 更新订单状态
      if (Boolean.TRUE.equals(paymentResult.get("success"))) {
        order.setStatus("PAID");
      } else {
        order.setStatus("PAYMENT_FAILED");
      }

      return Map.of(
          "success", true,
          "message", "订单创建成功",
          "orderId", order.getId(),
          "totalAmount", totalAmount,
          "user", user.getUsername(),
          "product", product.getName(),
          "payment", paymentResult,
          "instanceInfo", productResponse.get("instanceInfo")
      );

    } catch (Exception e) {
      System.err.println("创建订单失败: " + e.getMessage());
      return Map.of("success", false, "message", "创建订单失败: " + e.getMessage());
    }
  }

  /**
   * 处理支付 - 使用编程式熔断器
   */
  private Map<String, Object> processPayment(OrderDTO order, UserDTO user) {
    // 使用编程式熔断器,可以更灵活地控制
    return circuitBreakerFactory.create(PAYMENT_SERVICE_CB).run(() -> {
      Map<String, Object> paymentRequest = Map.of(
          "orderId", order.getId(),
          "amount", order.getTotalAmount(),
          "userId", user.getId(),
          "userName", user.getUsername(),
          "paymentMethod", "ALIPAY"
      );

      // 调用支付服务
      Map<String, Object> result = paymentServiceClient.createPayment(paymentRequest);
      System.out.println("支付服务调用结果: " + result);
      return result;
    }, throwable -> {
      // 降级处理
      System.err.println("支付服务熔断降级: " + throwable.getMessage());
      return Map.of(
          "success", false,
          "message", "支付服务暂时不可用",
          "fallback", true,
          "paymentId", null
      );
    });
  }

  /**
   * 创建订单的降级方法
   * 当熔断器打开或重试耗尽时调用
   */
  public Map<String, Object> createOrderFallback(Map<String, Object> orderRequest, Exception e) {
    System.err.println("订单创建熔断降级,原因: " + e.getMessage());

    return Map.of(
        "success", false,
        "message", "系统繁忙,请稍后重试",
        "fallback", true,
        "orderId", null
    );
  }

  /**
   * 获取订单详情 - 使用熔断器保护
   */
  @CircuitBreaker(name = USER_SERVICE_CB, fallbackMethod = "getOrderFallback")
  public OrderDTO getOrderWithUser(Long orderId) {
    OrderDTO order = getOrder(orderId);
    if (order != null) {
      // 调用用户服务获取用户详情
      UserDTO user = userServiceClient.getUserById(order.getUserId());
      // 这里可以将user信息设置到order中,为了简单起见,我们只是打印
      System.out.println("订单用户: " + user);
    }
    return order;
  }

  /**
   * 获取订单的降级方法
   */
  public OrderDTO getOrderFallback(Long orderId, Exception e) {
    System.err.println("获取订单熔断降级: " + e.getMessage());
    // 返回基础订单信息,不包含用户详情
    return getOrder(orderId);
  }

  /**
   * 模拟高并发场景下的服务调用 - 使用限流器
   */
  @CircuitBreaker(name = PRODUCT_SERVICE_CB, fallbackMethod = "bulkCheckInventoryFallback")
  public Map<String, Object> bulkCheckInventory(List<Long> productIds) {
    Map<String, Object> results = new java.util.HashMap<>();

    for (Long productId : productIds) {
      try {
        Map<String, Object> productResponse = productServiceClient.getProductById(productId);
        if (productResponse != null && productResponse.get("product") != null) {
          Map<String, Object> productMap = (Map<String, Object>) productResponse.get("product");
          results.put(productId.toString(), Map.of(
              "name", productMap.get("name"),
              "stock", productMap.get("stock"),
              "available", true
          ));
        } else {
          results.put(productId.toString(), Map.of(
              "available", false,
              "reason", "商品不存在"
          ));
        }
      } catch (Exception e) {
        results.put(productId.toString(), Map.of(
            "available", false,
            "reason", "服务调用失败: " + e.getMessage()
        ));
      }
    }

    return Map.of(
        "success", true,
        "results", results,
        "totalChecked", productIds.size()
    );
  }

  /**
   * 批量检查库存的降级方法
   */
  public Map<String, Object> bulkCheckInventoryFallback(List<Long> productIds, Exception e) {
    System.err.println("批量检查库存熔断降级: " + e.getMessage());

    // 返回降级结果,标记所有商品为不可用
    Map<String, Object> results = new java.util.HashMap<>();
    for (Long productId : productIds) {
      results.put(productId.toString(), Map.of(
          "available", false,
          "reason", "服务暂时不可用",
          "fallback", true
      ));
    }

    return Map.of(
        "success", false,
        "message", "库存服务暂时不可用",
        "results", results,
        "fallback", true
    );
  }

  /**
   * 获取订单详情(基础方法,无熔断)
   */
  public OrderDTO getOrder(Long orderId) {
    return orders.stream()
        .filter(o -> o.getId().equals(orderId))
        .findFirst()
        .orElse(null);
  }

  /**
   * 获取用户订单列表
   */
  public List<OrderDTO> getUserOrders(Long userId) {
    return orders.stream()
        .filter(o -> o.getUserId().equals(userId))
        .toList();
  }

  /**
   * 将Map转换为ProductDTO
   */
  private ProductDTO mapToProductDTO(Map<String, Object> productMap) {
    ProductDTO product = new ProductDTO();
    product.setId(Long.valueOf(productMap.get("id").toString()));
    product.setName((String) productMap.get("name"));
    product.setCategory((String) productMap.get("category"));
    product.setPrice(Double.valueOf(productMap.get("price").toString()));
    product.setStock(Integer.valueOf(productMap.get("stock").toString()));
    product.setDescription((String) productMap.get("description"));
    return product;
  }
}

订单控制器(Resilience4j版本)
package com.lihaozhe.orderservice.controller;

import com.lihaozhe.orderservice.dto.OrderDTO;
import com.lihaozhe.orderservice.service.OrderService;
import com.lihaozhe.userservice.dto.UserDTO;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

/**
 * 订单服务控制器 - Resilience4j版本
 * 演示熔断器、降级等容错机制
 *
 * @author 李昊哲
 * @version 1.0.0
 */
@RestController
@RequestMapping("/api/orders")
public class OrderController {

  private final OrderService orderService;

  public OrderController(OrderService orderService) {
    this.orderService = orderService;
  }

  /**
   * 创建订单 - 受熔断器保护
   * POST /api/orders
   */
  @PostMapping
  public Map<String, Object> createOrder(@RequestBody Map<String, Object> orderRequest) {
    return orderService.createOrder(orderRequest);
  }

  /**
   * 获取订单详情 - 受熔断器保护
   * GET /api/orders/{orderId}/with-user
   */
  @GetMapping("/{orderId}/with-user")
  public OrderDTO getOrderWithUser(@PathVariable Long orderId) {
    return orderService.getOrderWithUser(orderId);
  }

  /**
   * 获取订单详情 - 无熔断保护
   * GET /api/orders/{orderId}
   */
  @GetMapping("/{orderId}")
  public OrderDTO getOrder(@PathVariable Long orderId) {
    return orderService.getOrder(orderId);
  }

  /**
   * 获取用户的所有订单
   * GET /api/orders/user/{userId}
   */
  @GetMapping("/user/{userId}")
  public List<OrderDTO> getUserOrders(@PathVariable Long userId) {
    return orderService.getUserOrders(userId);
  }

  /**
   * 批量检查库存 - 受熔断器保护
   * POST /api/orders/bulk-check-inventory
   */
  @PostMapping("/bulk-check-inventory")
  public Map<String, Object> bulkCheckInventory(@RequestBody Map<String, Object> request) {
    List<Long> productIds = (List<Long>) request.get("productIds");
    return orderService.bulkCheckInventory(productIds);
  }

  /**
   * 模拟支付服务故障的测试端点
   * GET /api/orders/payment-test
   */
  @GetMapping("/payment-test")
  public Map<String, Object> paymentTest() {
    // 模拟一个订单请求
    Map<String, Object> orderRequest = Map.of(
        "userId", 1L,
        "productId", 1L,
        "quantity", 1
    );

    return orderService.createOrder(orderRequest);
  }

  /**
   * 健康检查端点
   * GET /api/orders/health
   */
  @GetMapping("/health")
  public Map<String, Object> health() {
    return Map.of(
        "status", "UP",
        "service", "order-service",
        "timestamp", System.currentTimeMillis(),
        "resilience4jEnabled", true
    );
  }
}

3. Payment Service支付服务(模拟故障)

支付控制器增强(添加故障模拟)
package com.lihaozhe.paymentservice.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 支付服务控制器
 * 模拟支付处理流程
 *
 * @author 李昊哲
 * @version 1.0.0
 */
@RestController
@RequestMapping("/api/payments")
public class PaymentController {

  // 支付记录存储
  private final Map<String, Map<String, Object>> payments = new ConcurrentHashMap<>();
  private final AtomicLong paymentIdCounter = new AtomicLong(1000);

  @Value("${server.port}")
  private String serverPort;
  // 模拟故障的计数器
  private int failureCount = 0;
  private boolean serviceDown = false;

  /**
   * 创建支付 - 带有故障模拟
   */
  @PostMapping
  public Map<String, Object> createPayment(@RequestBody Map<String, Object> paymentRequest) {
    // 模拟服务宕机
    if (serviceDown) {
      throw new RuntimeException("支付服务暂时不可用,模拟服务宕机");
    }

    // 模拟随机故障:每3次请求就有1次失败
    failureCount++;
    if (failureCount % 3 == 0) {
      throw new RuntimeException("模拟支付服务内部错误");
    }

    Long orderId = Long.valueOf(paymentRequest.get("orderId").toString());
    Double amount = Double.valueOf(paymentRequest.get("amount").toString());
    Long userId = Long.valueOf(paymentRequest.get("userId").toString());

    System.out.println("处理支付请求 - 订单ID: " + orderId + ", 金额: " + amount + ", 用户ID: " + userId);

    try {
      // 模拟支付处理时间,有时慢有时快
      long processingTime = (long) (Math.random() * 3000) + 500; // 500-3500ms
      Thread.sleep(processingTime);

      // 生成支付ID
      String paymentId = "PAY_" + paymentIdCounter.incrementAndGet();

      // 模拟支付成功(80%成功率)
      boolean success = Math.random() > 0.2;
      String status = success ? "SUCCESS" : "FAILED";

      // 保存支付记录
      Map<String, Object> payment = Map.of(
          "paymentId", paymentId,
          "orderId", orderId,
          "amount", amount,
          "userId", userId,
          "status", status,
          "paymentTime", System.currentTimeMillis(),
          "processedBy", "payment-service:" + serverPort,
          "processingTime", processingTime
      );

      payments.put(paymentId, payment);

      return Map.of(
          "success", success,
          "paymentId", paymentId,
          "status", status,
          "message", success ? "支付成功" : "支付失败,请重试",
          "processedBy", "payment-service:" + serverPort,
          "processingTime", processingTime
      );

    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      return Map.of(
          "success", false,
          "paymentId", null,
          "status", "FAILED",
          "message", "支付处理被中断"
      );
    } catch (Exception e) {
      return Map.of(
          "success", false,
          "paymentId", null,
          "status", "FAILED",
          "message", "支付处理异常: " + e.getMessage()
      );
    }
  }

  /**
   * 手动触发服务故障 - 用于测试熔断器
   * POST /api/payments/fault-trigger
   */
  @PostMapping("/fault-trigger")
  public Map<String, Object> triggerFault(@RequestBody Map<String, Object> faultRequest) {
    String faultType = (String) faultRequest.get("faultType");

    return switch (faultType) {
      case "serviceDown" -> {
        serviceDown = true;
        yield Map.of("message", "支付服务已设置为宕机状态");
      }
      case "serviceUp" -> {
        serviceDown = false;
        yield Map.of("message", "支付服务已恢复");
      }
      case "resetCounter" -> {
        failureCount = 0;
        yield Map.of("message", "故障计数器已重置");
      }
      default -> Map.of("message", "未知故障类型");
    };
  }

  /**
   * 获取服务状态
   * GET /api/payments/status
   */
  @GetMapping("/status")
  public Map<String, Object> getServiceStatus() {
    return Map.of(
        "serviceDown", serviceDown,
        "failureCount", failureCount,
        "totalPayments", payments.size(),
        "port", serverPort
    );
  }

  /**
   * 查询支付状态
   * POST /api/payments/{paymentId}/status
   */
  @PostMapping("/{paymentId}/status")
  public Map<String, Object> getPaymentStatus(@RequestBody Map<String, Object> request) {
    String paymentId = request.get("paymentId").toString();

    Map<String, Object> payment = payments.get(paymentId);
    if (payment == null) {
      return Map.of(
          "status", "NOT_FOUND",
          "message", "支付记录不存在",
          "paymentId", paymentId
      );
    }

    return Map.of(
        "status", payment.get("status"),
        "paymentId", paymentId,
        "orderId", payment.get("orderId"),
        "amount", payment.get("amount"),
        "paymentTime", payment.get("paymentTime"),
        "processedBy", payment.get("processedBy")
    );
  }

  /**
   * 退款处理
   * POST /api/payments/refund
   */
  @PostMapping("/refund")
  public Map<String, Object> refund(@RequestBody Map<String, Object> refundRequest) {
    String paymentId = refundRequest.get("paymentId").toString();
    Double refundAmount = Double.valueOf(refundRequest.get("amount").toString());

    System.out.println("处理退款请求 - 支付ID: " + paymentId + ", 退款金额: " + refundAmount);

    // 模拟退款处理
    try {
      Thread.sleep(150);

      Map<String, Object> payment = payments.get(paymentId);
      if (payment == null) {
        return Map.of(
            "success", false,
            "message", "原支付记录不存在",
            "refundId", null
        );
      }

      // 生成退款ID
      String refundId = "REFUND_" + System.currentTimeMillis();

      return Map.of(
          "success", true,
          "refundId", refundId,
          "paymentId", paymentId,
          "refundAmount", refundAmount,
          "message", "退款处理成功",
          "processedBy", "payment-service:" + serverPort
      );

    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      return Map.of(
          "success", false,
          "message", "退款处理被中断"
      );
    }
  }

  /**
   * 获取所有支付记录(管理用)
   * GET /api/payments/all
   */
  @GetMapping("/all")
  public Map<String, Object> getAllPayments() {
    return Map.of(
        "totalPayments", payments.size(),
        "payments", payments.values(),
        "service", "payment-service:" + serverPort
    );
  }

  /**
   * 健康检查
   * GET /api/payments/health
   */
  @GetMapping("/health")
  public Map<String, Object> health() {
    return Map.of(
        "status", "UP",
        "service", "payment-service",
        "port", serverPort,
        "timestamp", System.currentTimeMillis(),
        "totalPayments", payments.size()
    );
  }
}

开发思路和过程

1. Resilience4j核心概念:

  • 熔断器 (Circuit Breaker):防止连锁故障,快速失败
  • 重试 (Retry):自动重试失败的操作
  • 限流器 (Rate Limiter):限制调用频率
  • 时间限制器 (Time Limiter):设置调用超时时间
  • 隔板 (Bulkhead):隔离不同资源的调用

2. Resilience4j使用方式:

  1. 注解方式:使用@CircuitBreaker@Retry等注解
  2. 编程方式:使用CircuitBreakerFactory等编程式API

3. 熔断器状态转换:

  • CLOSED:正常状态,请求正常通过
  • OPEN:熔断状态,请求直接失败,不调用后端服务
  • HALF_OPEN:半开状态,允许部分请求通过测试后端是否恢复

4. 配置策略:

  • 失败率阈值:超过阈值时打开熔断器
  • 等待时间:熔断器打开后等待多长时间进入半开状态
  • 最小调用次数:计算失败率所需的最小调用次数

运行测试

启动顺序:

  1. 启动Eureka Server (端口8761)
  2. 启动User Service (端口8081)
  3. 启动Product Service实例1 (端口8082)
  4. 启动Payment Service (端口8086)
  5. 启动Order Service (端口8083)

测试步骤:

# 1. 查看支付服务状态
curl http://localhost:8086/api/payments/status

# 2. 测试正常订单创建
curl -X POST http://localhost:8083/api/orders \
  -H "Content-Type: application/json" \
  -d '{"userId": 1, "productId": 1, "quantity": 1}'

# 3. 多次调用支付测试,观察熔断器行为
for i in {1..10}; do
  curl http://localhost:8083/api/orders/payment-test
  echo ""
  sleep 1
done

# 4. 手动触发支付服务故障
curl -X POST http://localhost:8086/api/payments/fault-trigger \
  -H "Content-Type: application/json" \
  -d '{"faultType": "serviceDown"}'

# 5. 再次测试订单创建,观察降级行为
curl -X POST http://localhost:8083/api/orders \
  -H "Content-Type: application/json" \
  -d '{"userId": 1, "productId": 1, "quantity": 1}'

# 6. 恢复支付服务
curl -X POST http://localhost:8086/api/payments/fault-trigger \
  -H "Content-Type: application/json" \
  -d '{"faultType": "serviceUp"}'

# 7. 测试批量检查库存
curl -X POST http://localhost:8083/api/orders/bulk-check-inventory \
  -H "Content-Type: application/json" \
  -d '{"productIds": [1, 2, 3]}'

# 8. 查看熔断器状态(通过Actuator端点)
curl http://localhost:8083/actuator/health
curl http://localhost:8083/actuator/circuitbreakers

观察Resilience4j特性:

  • 多次调用支付测试,观察失败率超过阈值时熔断器打开
  • 熔断器打开后,请求直接返回降级结果,不再调用支付服务
  • 等待一段时间后,熔断器进入半开状态,允许部分请求通过
  • 查看Actuator端点,了解熔断器状态和指标

这一章我们学习了Resilience4j熔断机制,实现了系统的弹性容错能力。

在下一章中,我们将学习Spring Cloud Gateway网关,实现统一的API入口和路由功能。

更多推荐