SpringCloud系列之Hystrix业务异常处理实战

SpringCloud系列之Hystrix业务异常处理实战

一、问题提出

在SpringCloud实践过程中,你是否也有以下疑问:

系统运作过程中,服务端抛出业务异常,FeignClient(Hystrix命令)执行失败并触发服务降级处理逻辑,再调用几次,你发现服务端接不到请求了,直接进入了Hystrix的“服务降级处理逻辑”,百思不得其解。

这也算是当时SpringCloud实践中比较棘手的问题之一了,就算你搜遍百度、Google也搜不到合理的解释和解决方案,啊,西湖的水,我的泪…

二、解决思路分析

Cloud组件调用流程:消费者发起请求 -> Ribbon -> Hystrix -> Feign -> 提供者

既然这是一个自定义的业务异常,自然需要Feign、Hystrix识别到这是一个业务异常,为了达到这一目标,我们需要当服务端抛出业务异常时,解决以下两个问题:

  1. Feign如何包装错误信息
  2. Hystrix如何跳过故障度量逻辑、服务降级逻辑

三、深入GayHub

针对问题1,笔者在源码中翻到了ErrorDecoder,感兴趣的朋友可以看以下源代码。

Feign中如果服务返回的状态码不是200,就会进入到Feign的ErrorDecoder中,因此如果我们要解析异常信息,就要重写ErrorDecoder

针对问题2,笔者带着疑问,最终在GayHub的issues中觅得了“佳人”,以下是HystrixBadRequestException异常说明原文。

All exceptions thrown from the run() method except for HystrixBadRequestException count as failures and trigger getFallback() and circuit-breaker logic.

You can wrap the exception that you would like to throw in HystrixBadRequestException and retrieve it via getCause(). The HystrixBadRequestException is intended for use cases such as reporting illegal arguments or non-system failures that should not count against the failure metrics and should not trigger fallback logic.

Failure Type Exception class Exception.cause subject to fallback
FAILURE HystrixRuntimeException underlying exception (user-controlled) YES
TIMEOUT HystrixRuntimeException j.u.c.TimeoutException YES
SHORT_CIRCUITED HystrixRuntimeException j.l.RuntimeException YES
THREAD_POOL_REJECTED HystrixRuntimeException j.u.c.RejectedExecutionException YES
SEMAPHORE_REJECTED HystrixRuntimeException j.l.RuntimeException YES
BAD_REQUEST HystrixBadRequestException underlying exception (user-controlled) NO

简单翻译下就是,发生非HystrixBadRequestException异常时将会执行失败并触发服务降级处理逻辑,参数异常以及非系统异常不应计入故障度量以及服务降级处理,你可以包装异常的cause到HystrixBadRequestException中, 文中还附上了5中会被fallback的失败类型。

  1. FAILURE:执行失败,抛出异常。
  2. TIMEOUT:执行超时。
  3. SHORT_CIRCUITED:断路器打开。
  4. THREAD_POOL_REJECTED:线程池拒绝。
  5. SEMAPHORE_REJECTED:信号量拒绝。

四、SpringCloud实践方案

Feign利用HttpStatus来作为是否为异常请求的判断依据,在开发过程中可以定义一系列的业务异常状态码区间,例如笔者就比较喜欢把400-500作为业务异常码段,然后将这些错误码段的异常处理为HystrixBadRequestException。

/**
 * Feign的错误解码器
 * @version Revision: 0.0.1
 * @author: weihuang.peng
 * @Date: 2019-03-25
 */
public class FeignErrorDecode implements ErrorDecoder {
    @Override
    public Exception decode(String methodKey, Response response) {
        // 这一部分的异常将会变成子系统的异常, 不会进入hystrix的fallback方法,将会进入ErrorFilter的过滤链路
        if (response.status() >= HttpStatus.BAD_REQUEST.value() && response.status() < HttpStatus.INTERNAL_SERVER_ERROR.value()) {
            try {
                InputStream is = response.body().asInputStream();
                Result receipt = JSON.parseObject(IOUtils.toString(is), Result.class);
                return BaseException.getInstance(receipt);
            } catch (Exception e) {
            }
        }

        // 这一部分会进入fallback
        return feign.FeignException.errorStatus(methodKey, response);
    }
}

@Configuration
public class FeignConfiguration {

    /**
     * 业务异常解码器
     * @return
     */
    @Bean
    public ErrorDecoder getErrorDecoder() {
        return new FeignErrorDecode();
    }
}


这里有一个关注点:

  1. BaseException需要继承HystrixBadRequestException
  2. Feign的ErrorDecoder需要对业务异常的HttpStatus进行定义

五、源码分享

以下是笔者业务异常包装的案例,感兴趣的朋友可以关注以下。

六、引用参考


原文:https://my.oschina.net/pwh19920920/blog/4294556