You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Resilience4j熔断器下游恢复后无法从半开状态切回闭合状态

Resilience4j 熔断器半开状态下忽略异常不计入成功计数问题排查

问题背景

我部署了order-service、inventory-service两个微服务,order-service会调用inventory-service校验订购商品的库存,仅当订单请求内所有商品均有库存时才会完成下单。

  • 当inventory-service宕机或响应过慢时,熔断器逻辑触发
  • 因商品缺库存导致的下单失败不属于熔断器的失败判定场景
    在熔断器从未从闭合状态切换到半开状态前,上述逻辑均符合预期。

异常现象

  • 若连续5次以上请求均因商品缺库存下单失败,熔断器不会切换到开路状态,符合预期
  • 将inventory-service停服后,3次失败请求会触发熔断器切换到开路状态,随后自动切换到半开状态,也符合预期
  • 但当inventory-service恢复服务后,若发起的所有请求均包含一个或多个缺库存商品,熔断器会持续停留在half_open状态,不符合预期

缺库存属于业务正常场景,本应累加成功缓冲调用计数,但实际并未生效;从actuator监控信息来看,这类调用既没有被计为失败,也没有被计为成功。

补充说明:如果发起足够多的库存充足、可正常下单的请求,熔断器可以正常切回闭合状态。疑问点:即使所有请求都是缺库存的业务场景,被配置为忽略的异常难道不应该算作成功调用吗?

相关配置与代码

熔断器配置(application.properties)

resilience4j.circuitbreaker.instances.inventory.register-health-indicator=true
resilience4j.circuitbreaker.instances.inventory.event-consumer-buffer-size=10
resilience4j.circuitbreaker.instances.inventory.sliding-window-type=COUNT_BASED
resilience4j.circuitbreaker.instances.inventory.sliding-window-size=5
resilience4j.circuitbreaker.instances.inventory.failure-rate-threshold=50
resilience4j.circuitbreaker.instances.inventory.wait-duration-in-open-state=5s
resilience4j.circuitbreaker.instances.inventory.permitted-number-of-calls-in-half-open-state=3
resilience4j.circuitbreaker.instances.inventory.automatic-transition-from-open-to-half-open-enabled=true
resilience4j.circuitbreaker.instances.inventory.ignore-exceptions=com.mayrevision.orderservice.exception.OrderItemNotFoundException

配置修改记录:已将原配置项resilience4j.circuitbreaker.instances.inventory.ignore-exceptions[0]=com.mayrevision.orderservice.exception.OrderItemNotFoundException修改为上述无索引的配置格式。

自定义异常处理器

@ControllerAdvice
@ResponseStatus
public class OrderItemNotFoundExceptionHandler extends ResponseEntityExceptionHandler {
    @ExceptionHandler(OrderItemNotFoundException.class)
    public ResponseEntity<ErrorResponse> getErrorMessage(OrderItemNotFoundException exception) {
        ErrorResponse response = new ErrorResponse(HttpStatus.NOT_ACCEPTABLE, exception.getMessage());
        return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE).body(response);
    }
}

控制器层代码

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
@CircuitBreaker(name = "inventory", fallbackMethod = "fallBackMethod")
public String placeOrder(@RequestBody OrderRequest orderRequest) throws OrderItemNotFoundException {
    orderService.placeOrder(orderRequest);
    return "Order placed successfully";
}

public String fallBackMethod(OrderRequest orderRequest, RuntimeException runtimeException) {
    return "The order could not be placed. Please try back after some time.";
}

问题原因与解决方案

根本原因

Resilience4j 熔断器的ignore-exceptions配置逻辑不是将标记的异常算作成功调用,而是直接将这类异常从统计维度完全排除:既不计入失败调用,也不计入成功调用,也不会占用半开状态下的许可调用计数配额。

半开状态下熔断器的状态切换逻辑是:统计permitted-number-of-calls-in-half-open-state配置的许可数量内的有效调用结果,当失败率低于阈值时切回闭合状态,高于阈值则切回开路状态。你配置的半开许可数是3,而所有抛出OrderItemNotFoundException的请求都被直接排除在统计之外,永远凑不够3个有效统计调用,自然会一直卡在半开状态。

闭合状态下滑动窗口统计是按计数滚动的,被忽略的异常不占窗口计数,连续抛出被忽略异常时窗口内没有失败记录,所以不会触发开路,这也是之前闭合状态下逻辑符合预期的原因。

另外当前的降级方法签名也存在问题:降级方法只声明了捕获RuntimeException类型参数,但OrderItemNotFoundException如果是受检异常(直接继承Exception而非RuntimeException)的话,抛出时根本不会进入降级逻辑,不过这不是当前卡半开的核心原因。

修复方案

  1. 不要用ignore-exceptions处理业务预期异常:业务上的库存不足属于正常业务返回,不应该用抛出异常的方式返回给切面层,建议调整业务逻辑:库存不足时直接返回对应的业务结果,而非抛出OrderItemNotFoundException,让熔断器切面能捕获到正常返回,计入成功调用计数。
  2. 如果确实要保留异常抛出逻辑,不要把这类业务异常配置到ignore-exceptions中,而是通过record-exceptions明确指定需要判定为失败的异常类型(比如服务调用超时异常、连接异常等),其余异常默认都会被判定为业务正常完成,计入成功计数,就能正常触发半开状态的统计判断。
  3. 修正降级方法签名:保证降级方法的异常参数可以覆盖所有可能从切面抛出的异常类型,避免异常穿透。

内容的提问来源于stack exchange,提问作者Chan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 03:57:37