SpringBoot 开发双11商品服务系统:服务降级与熔断实现
下面我将展示如何使用SpringBoot结合Resilience4j实现双11商品服务系统的服务降级与熔断功能,包含完整的代码示例。
1. 项目基础配置
1.1 添加依赖
xml
xml
<!-- pom.xml -->
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Resilience4j Starter -->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
<version>1.7.1</version>
</dependency>
<!-- Actuator for monitoring -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
1.2 配置文件
yaml
yaml
# application.yml
server:
port: 8080
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always
resilience4j:
circuitbreaker:
configs:
default:
registerHealthIndicator: true
slidingWindowSize: 10
minimumNumberOfCalls: 5
permittedNumberOfCallsInHalfOpenState: 3
automaticTransitionFromOpenToHalfOpenEnabled: true
waitDurationInOpenState: 5s
failureRateThreshold: 50
eventConsumerBufferSize: 10
instances:
productService:
baseConfig: default
registerHealthIndicator: true
slidingWindowSize: 20
failureRateThreshold: 40
inventoryService:
baseConfig: default
failureRateThreshold: 30
retry:
instances:
default:
maxAttempts: 3
waitDuration: 100ms
retryExceptions:
- java.lang.RuntimeException
ratelimiter:
instances:
default:
limitForPeriod: 10
limitRefreshPeriod: 1s
timeoutDuration: 100ms
2. 核心业务实现
2.1 商品服务接口
scss
java
// ProductService.java
public interface ProductService {
/**
* 获取商品详情
*/
ProductDetail getProductDetail(Long productId);
/**
* 获取商品价格(模拟可能失败的服务)
*/
BigDecimal getProductPrice(Long productId);
/**
* 获取库存信息(模拟可能超时的服务)
*/
InventoryInfo getInventoryInfo(Long productId);
}
2.2 商品服务实现
kotlin
java
// ProductServiceImpl.java
@Service
@RequiredArgsConstructor
public class ProductServiceImpl implements ProductService {
private final InventoryClient inventoryClient;
private final PriceClient priceClient;
@Override
public ProductDetail getProductDetail(Long productId) {
// 模拟从数据库获取商品基本信息
Product product = getProductFromDB(productId);
// 获取价格信息(带熔断和降级)
BigDecimal price = priceClient.getProductPrice(productId);
// 获取库存信息(带熔断、降级和重试)
InventoryInfo inventory = inventoryClient.getInventoryInfo(productId);
return ProductDetail.builder()
.productId(product.getId())
.name(product.getName())
.description(product.getDescription())
.price(price)
.stock(inventory.getAvailableStock())
.build();
}
@Override
@CircuitBreaker(name = "productService", fallbackMethod = "fallbackPrice")
public BigDecimal getProductPrice(Long productId) {
// 模拟随机失败
if (Math.random() > 0.7) {
throw new RuntimeException("Price service unavailable");
}
// 模拟耗时操作
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return BigDecimal.valueOf(99.99);
}
@Override
@CircuitBreaker(name = "inventoryService", fallbackMethod = "fallbackInventory")
@Retry(name = "default", fallbackMethod = "fallbackInventory")
@RateLimiter(name = "default", fallbackMethod = "fallbackInventory")
public InventoryInfo getInventoryInfo(Long productId) {
// 模拟随机超时
if (Math.random() > 0.8) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
return new InventoryInfo(productId, 100, true);
}
// 降级方法:价格服务不可用时返回默认价格
public BigDecimal fallbackPrice(Long productId, Exception e) {
return BigDecimal.valueOf(99.99); // 返回默认价格
}
// 降级方法:库存服务不可用时返回默认库存
public InventoryInfo fallbackInventory(Long productId, Exception e) {
return new InventoryInfo(productId, 0, false); // 返回库存不足
}
// 降级方法:限流时返回提示
public InventoryInfo fallbackInventory(Long productId) {
return new InventoryInfo(productId, -1, false); // 返回系统繁忙
}
private Product getProductFromDB(Long productId) {
// 模拟数据库查询
return Product.builder()
.id(productId)
.name("双11特价商品-" + productId)
.description("双11限时抢购,超值优惠")
.build();
}
}
2.3 Feign客户端实现(模拟外部服务调用)
less
java
// InventoryClient.java
@FeignClient(name = "inventory-service", url = "http://localhost:8081")
public interface InventoryClient {
@GetMapping("/inventory/{productId}")
InventoryInfo getInventoryInfo(@PathVariable Long productId);
}
// PriceClient.java
@FeignClient(name = "price-service", url = "http://localhost:8082")
public interface PriceClient {
@GetMapping("/price/{productId}")
BigDecimal getProductPrice(@PathVariable Long productId);
}
3. 控制器层
kotlin
java
// ProductController.java
@RestController
@RequestMapping("/api/products")
@RequiredArgsConstructor
public class ProductController {
private final ProductService productService;
@GetMapping("/{productId}")
public ResponseEntity<ProductDetail> getProductDetail(
@PathVariable Long productId,
@RequestParam(required = false) Boolean useFallback) {
// 模拟根据参数决定是否强制使用降级逻辑
if (Boolean.TRUE.equals(useFallback)) {
// 手动触发降级逻辑(实际中不需要这样,仅用于演示)
try {
throw new RuntimeException("Simulated failure");
} catch (Exception e) {
ProductDetail fallback = ProductDetail.builder()
.productId(productId)
.name("降级商品名称")
.description("服务降级中,请稍后再试")
.price(BigDecimal.ZERO)
.stock(0)
.build();
return ResponseEntity.ok(fallback);
}
}
return ResponseEntity.ok(productService.getProductDetail(productId));
}
// 专门测试熔断的接口
@GetMapping("/{productId}/price")
@CircuitBreaker(name = "productService", fallbackMethod = "fallbackPrice")
public ResponseEntity<BigDecimal> getPrice(@PathVariable Long productId) {
return ResponseEntity.ok(productService.getProductPrice(productId));
}
public ResponseEntity<BigDecimal> fallbackPrice(Long productId, Exception e) {
return ResponseEntity.ok(BigDecimal.valueOf(88.88)); // 降级价格
}
}
4. 数据模型
kotlin
java
// Product.java
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Product {
private Long id;
private String name;
private String description;
}
// ProductDetail.java
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ProductDetail {
private Long productId;
private String name;
private String description;
private BigDecimal price;
private int stock;
}
// InventoryInfo.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class InventoryInfo {
private Long productId;
private int availableStock;
private boolean isActive;
}
5. 全局异常处理
typescript
java
// GlobalExceptionHandler.java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(CircuitBreakerOpenException.class)
public ResponseEntity<Map<String, String>> handleCircuitBreakerOpen(CircuitBreakerOpenException e) {
Map<String, String> body = new HashMap<>();
body.put("status", "503");
body.put("message", "服务暂时不可用,请稍后再试");
body.put("reason", "熔断器已打开,防止级联故障");
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(body);
}
@ExceptionHandler(RequestNotPermitted.class)
public ResponseEntity<Map<String, String>> handleRateLimiter(RequestNotPermitted e) {
Map<String, String> body = new HashMap<>();
body.put("status", "429");
body.put("message", "系统繁忙,请稍后再试");
body.put("reason", "请求速率超过限制");
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body(body);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<Map<String, String>> handleGeneralException(Exception e) {
Map<String, String> body = new HashMap<>();
body.put("status", "500");
body.put("message", "服务器内部错误");
body.put("error", e.getMessage());
return ResponseEntity.internalServerError().body(body);
}
}
6. 健康检查端点(可选)
less
java
// HealthCheckController.java
@RestController
@RequestMapping("/health")
public class HealthCheckController {
@GetMapping("/circuitbreakers")
public ResponseEntity<Map<String, Object>> getCircuitBreakerStatus(
@Autowired CircuitBreakerRegistry registry) {
Map<String, Object> status = new HashMap<>();
registry.getAllCircuitBreakers().forEach(cb -> {
CircuitBreaker.Metrics metrics = cb.getMetrics();
status.put(cb.getName(), Map.of(
"state", cb.getState(),
"failureRate", metrics.getFailureRate(),
"successfulCalls", metrics.getNumberOfSuccessfulCalls(),
"failedCalls", metrics.getNumberOfFailedCalls()
));
});
return ResponseEntity.ok(status);
}
}
7. 测试与验证
7.1 测试熔断器行为
-
启动应用后,访问
/api/products/1
多次,观察正常响应 -
模拟服务失败:
- 修改
getProductPrice
方法,将失败概率提高到> 0.5
- 继续访问接口,当失败率超过阈值(50%)时,熔断器会打开
- 此时所有请求都会直接走降级逻辑
- 修改
-
等待配置的等待时间(5秒)后,熔断器会进入半开状态
-
半开状态下,部分请求会被允许通过,如果成功则熔断器关闭
7.2 测试限流行为
- 使用压力测试工具(如JMeter)快速发送请求到
/api/products/1/inventory
- 当QPS超过配置的限流阈值(10次/秒)时,部分请求会返回429状态码
7.3 测试重试机制
- 修改
getInventoryInfo
方法,在第一次调用时抛出异常 - 配置了3次重试,所以请求最多会尝试3次
- 如果所有重试都失败,则走降级逻辑
8. 监控与告警
通过访问 /actuator
端点可以查看:
/actuator/health
- 系统健康状态,包含熔断器状态/actuator/circuitbreakers
- 熔断器详细状态/actuator/ratelimiters
- 限流器状态
可以配置Prometheus和Grafana来收集这些指标并可视化展示。
总结
本文展示了如何使用SpringBoot和Resilience4j实现双11商品服务系统的服务降级与熔断功能,包括:
- 熔断机制 :通过
@CircuitBreaker
注解保护可能失败的服务调用 - 降级策略:为每个关键服务提供合理的降级方案
- 重试机制:对可重试的失败进行自动重试
- 限流保护:防止系统被过多请求压垮
- 全局异常处理:统一处理各类异常情况
- 监控端点:提供系统运行状态的监控接口
这种实现方式能够有效提升商品服务系统在双11高并发场景下的稳定性和可用性,确保即使部分依赖服务出现问题,核心功能仍然可用,为用户提供良好的购物体验。