【问题标题】:Capture exception thrown by Hystrix fallback?捕获 Hystrix 回退引发的异常?
【发布时间】:2017-03-22 13:43:49
【问题描述】:

我正在设计一个服务外观,并且我有一个如下所示的方法签名:

public Policy getPolicy(long policyId) throws PolicyNotFoundException

如果没有发生任何错误,则返回一个 Policy 对象(简单 POJO)。如果未找到请求的策略,则抛出已检查的异常 PolicyNotFoundException(仅作为参考 - 当涉及应用程序中异常处理的最佳实践时,我们遵循 this article)。

服务外观层之上的层(在本例中为Spring MVC RestController)知道如何处理此类 PolicyNotFoundException 并返回适当的负载。

我正在尝试通过执行以下操作将其合并到 HystrixCommand 中:

@HystrixCommand(groupKey = "PolicyService", fallbackMethod = "getPolicySafe", ignoreExceptions = { PolicyNotFoundException.class })
public Policy getPolicy(long policyId) throws PolicyNotFoundException {
    LOGGER.info("Getting policy {}", policyId);

    // Simulate some error condition for testing purposes
    throw new RuntimeException("Something happened!");
}

private Policy getPolicySafe(long policyId, Throwable t) throws PolicyNotFoundException {
    LOGGER.warn("Falling back to circuit-breaker for getting policy {}", policyId, t);
    throw new PolicyNotFoundException(policyId);
}

基本上,我希望我的断路器的行为就像原始查找未找到该策略一样。我遇到的问题是我从后备方法抛出的异常在某处的翻译中丢失了。我最终在上层看到的异常是命令方法抛出的 RuntimeException,而不是回退方法抛出的异常。有没有解决的办法?我也不想更改原始方法的合同,也不想让上面的层知道除了在找不到策略的情况下必须捕获 PolicyNotFoundException 之外的任何内容。此处需要的任何内容都应在此服务外观层中捕获。

我们将不胜感激任何和所有的帮助。谢谢!

【问题讨论】:

    标签: spring-cloud hystrix spring-cloud-netflix


    【解决方案1】:

    所以根据@spencergibb 给出的链接——我可能在升级到 Hystrix 1.5.7 后找到了解决方案。此代码按预期工作

    PolicyRestController.java

    @RestController
    @RequestMapping("/policies")
    public class PoliciesApi {
      private static final Logger LOGGER = LoggerFactory.getLogger(PoliciesApi.class);
    
      @Autowired
      private PolicyService policyService;
    
      @RequestMapping(value = "/{policyId}", method = RequestMethod.GET, produces = { MediaTypes.POLICY_JSON_VALUE, MediaTypes.POLICY_XML_VALUE })
      public Policy getPolicy(@PathVariable long policyId) {
        try {
          // This just shown for simplicity. There is more to this method (input validation/etc)
          return this.policyService.getPolicy(policyId);
        }
        catch (PolicyNotFoundException ex) {
          // NotFoundException is a RuntimeException annotated with @ResponseStatus(HttpStatus.NOT_FOUND)
          // So the service returns a 404 to the client
          LOGGER.info("Policy {} wasn't found", ex.getPolicyId(), ex);
          throw new NotFoundException(String.format("Policy %s was not found", ex.getPolicyId()));
        }
      }
    }
    

    PolicyService.java

    public interface PolicyService {
        @Cacheable("allPolicies")
        public List<Policy> getPolicies();
    
        @Cacheable("policies")
        public Policy getPolicy(long policyId) throws PolicyNotFoundException;
    }
    

    PolicyServiceImpl.java:

    @Service
    public class PolicyServiceImpl implements PolicyService {
      @HystrixCommand(groupKey = "PolicyService", fallbackMethod = "getPolicySafe", ignoreExceptions = { PolicyNotFoundException.class })
      public Policy getPolicy(long policyId) throws PolicyNotFoundException {
        LOGGER.info("Getting policy {}", policyId);
    
        // Simulate some error condition for testing purposes
        throw new RuntimeException("Something happened!");
      }
    
      @HystrixCommand(groupKey = "PolicyService", ignoreExceptions = { PolicyNotFoundException.class }, raiseHystrixExceptions = { HystrixException.RUNTIME_EXCEPTION })
      private Policy getPolicySafe(long policyId) throws PolicyNotFoundException {
        // Here is we hit our fallback we want to log a warning & simply act as if the policy wasn't found by throwing the same contingency exception as the API does
        LOGGER.warn("Falling back to circuit-breaker for getting policy {}", policyId);
    
        throw new PolicyNotFoundException(policyId);
      }
    }
    

    【讨论】:

      【解决方案2】:

      虽然您的解决方案可能对您有用,但我注意到您的代码中有一些奇怪之处(我无法检查我的假设,所以我想请您检查一下)。

      1. 尽量避免在代码中使用检查异常,因为它是 难以维护。
      2. 根据您的代码,您将永远无法捕捉到 “PolicyNotFoundException”,因为您使用 raiseHystrixExceptions = { HystrixException.RUNTIME_EXCEPTION } 这意味着您不会获得自定义异常,因此 HystrixRuntimeException 将被传播。尝试如下重写您的代码,以便它应该 简化代码,也许可以解决您的一些问题:

      @Service
      public class PolicyServiceImpl implements PolicyService {
        @HystrixCommand(groupKey = "PolicyService", fallbackMethod = "getPolicySafe")
        public Policy getPolicy(long policyId) throws PolicyNotFoundException {
          LOGGER.info("Getting policy {}", policyId);
          throw new PolicyNotFoundException(); // throw real PolicyNotFoundException if policy is absent for the given id
        }
      
        @HystrixCommand(groupKey = "PolicyService")
        private Policy getPolicySafe(long policyId) throws PolicyNotFoundException {
          // Here is we hit our fallback we want to log a warning & simply act as if the policy wasn't found by throwing the same contingency exception as the API does
          LOGGER.warn("Falling back to circuit-breaker for getting policy {}", policyId);
      
          throw new PolicyNotFoundException(policyId);
        }
      }
      

      【讨论】:

      • 感谢您的提示。在电路开路的情况下,这仍然不起作用。当电路打开时,HystrixRuntimeException 被传播到调用者(在我的例子中是 Spring MVC RestController)。这“污染”了我的 RestController 类,因为它必须包含 HystrixRuntimeException 的捕获。我想将所有与 hystrix 相关的“东西”保留在它所属的服务外观层中。
      • @Eric 所以逻辑上是正确的,因为异常是由 hystrix 平台引发的。这是预期的行为,因此在这种情况下您必须捕获 HystrixRuntimeException。
      • 它只是感觉“肮脏”,尽管应用程序中的其他一些层需要知道甚至涉及到 Hystrix。如果我的 RestController 和服务外观层之间有一个接口,上面写着 public Policy getPolicy(long policyId) throws PolicyNotFoundException,则该方法的调用者不必也知道 Hystrix 可能在图片中,并且可能需要捕获 HystrixRuntimeException 并从中解开 PolicyNotFoundException。 ..
      • 只是为了明确一点:断路器是一个平台特性,所以如果一个命令因为断路器、超时等而被拒绝,这不是用户异常,你不能在这种情况下抛出 PolicyNotFOundExcepton,因为它是逻辑上不正确且技术上不可能,因为平台不知道客户端异常。我们可以考虑添加一些异常处理程序来捕获 hystrix 异常并将其包装在自定义异常中。
      • 当电路“打开”时 - 我假设后备方法仍然被调用,对吗?如果电路闭合并且命令失败与电路打开,行为不应该相同吗?
      【解决方案3】:

      这是 hystrix 的默认行为。 “如果命令有回退,那么只有触发回退逻辑的第一个异常才会传播给调用者”

      请参阅错误传播部分here

      【讨论】:

      • 对,我明白。有没有办法改变这种行为?
      • 这里有一些关于异常的讨论github.com/Netflix/Hystrix/issues/1344
      • 感谢您的链接 - 我没有看到。虽然这是朝着正确方向迈出的一步,但它并不能完全解决问题。应用程序的服务外观层是所有这些 hystrix“东西”发挥作用的地方。该解决方案“污染”了上面的层,并且使服务外观层的调用者必须通过捕获 HystrixRuntimeException、查找回退异常(通常是 FallbackInvocationException)来了解 Hystrix 所涉及的具体细节,然后必须阅读原因。
      • 实际上,我想我可能已经使用@spencergibb 提供的解决方案解决了这个问题。我会发布它作为答案。
      • 其实不是这样,项目 wiki 已经过时了,因为这个 PR:github.com/Netflix/Hystrix/pull/1389 中的行为发生了变化,所以我们需要更新文档
      【解决方案4】:

      我这样做:

      @Component
      public class HystrixClient {
      
        @HystrixCommand(ignoreExceptions = {ClientArgumentException.class})
        public POJO getPojo(String id)
              throws ClientNoDataFoundException, ClientArgumentException, ClientGeneralException {
      
          //call my service and return POJO
        }
      }
      
      @Component
      public TrueClientUsedForAnotherSerivce {
      
        @Autowired
        HystrixClient hystrixClient;
      
       public POJO getPojo(String id)
              throws ClientNoDataFoundException, ClientArgumentException, ClientGeneralException, ClientOpenCircuitException {
          try {           
              POJO result = hystrixClient.getCellular(id);            
      
              return result;
          }
          catch(HystrixRuntimeException e) {
              LOG.debug("The circuit is open");
              throw new ClientOpenCircuitException("Open circuit");
          }
      }   
      

      仅当@HystrixCommand 方法在另一个类中时才有效。

      【讨论】:

      • 在您的代码中添加一些描述。逻辑比一段代码更有帮助
      猜你喜欢
      • 2022-06-14
      • 2017-10-30
      • 2019-09-13
      • 1970-01-01
      • 2014-10-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-19
      相关资源
      最近更新 更多