11

我正在构建一个调用另一个微服务的 SpringBoot 微服务,并且自然希望使用 Spring Cloud 中包含的 Hystrix 和 Feign 客户端。我使用的是 Camden.SR5 版本。

对于来自 Feign 的任何超时、连接失败和 50 倍响应代码,我希望 Hystrix 能够正常工作:触发断路器并调用回退(如果已配置)等。默认情况下它会这样做,所以我很好.

但是对于 40x 响应代码,包括无效条目、错误格式的字段等,我希望 Hystrix 将这些异常传播给调用者,因此我也可以根据自己的选择处理它们。这不是我观察到的默认值。在 Spring Cloud 中如何配置 Hystrix/Feign 来做到这一点?

使用以下代码开箱即用:

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.hateoas.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@FeignClient(name = "dog-service", url = "http://...")
public interface DogsFeignClient {
  @RequestMapping(method = RequestMethod.POST, path = "/dogs")
  Resource<Dog> createDog(Dog dog);
}

生成此异常,它不能很好地将 40x 响应传递回调用者:

com.netflix.hystrix.exception.HystrixRuntimeException: DogsFeignClient#createDog(Dog) failed and no fallback available.
    at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:805) ~[hystrix-core-1.5.6.jar:1.5.6]
    ....lines ommited for brevity....
Caused by: feign.FeignException: status 400 reading DogsFeignClient#createDog(Dog); content:
{
  "errors" : [ {
    "entity" : "Dog",
    "property" : "numberOfLegs",
    "invalidValue" : "3",
    "message" : "All dogs must have 4 legs"
  } ]
}
    at feign.FeignException.errorStatus(FeignException.java:62) ~[feign-core-9.3.1.jar:na]
    at feign.codec.ErrorDecoder$Default.decode(ErrorDecoder.java:91) ~[feign-core-9.3.1.jar:na]
    at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:138) ~[feign-core-9.3.1.jar:na]
    at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:76) ~[feign-core-9.3.1.jar:na]
    at feign.hystrix.HystrixInvocationHandler$1.run(HystrixInvocationHandler.java:108) ~[feign-hystrix-9.3.1.jar:na]
    at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:301) ~[hystrix-core-1.5.6.jar:1.5.6]
    at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:297) ~[hystrix-core-1.5.6.jar:1.5.6]
    at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:46) ~[rxjava-1.1.10.jar:1.1.10]
    ... 26 common frames omitted

我当然可以查看包含 a 并隐藏在描述中的字段com.netflix.hystrix.exception.HystrixRuntimeException是JSON 响应本身,带有换行符等。但是字段 of是对自身的引用。有没有办法传播更深层次的异常而不是 HystrixRuntimeException?causefeign.FeignExceptioncausefeign.FeignException

还有一种方法可以让下游服务的响应中包含原始正文,所以我不必解构嵌套异常的消息字段?

4

1 回答 1

14

这可以使用单独的配置来实现,它将 400 包装在一个子类中HystrixBadRequestException并将它们扔给客户端代码。这些异常不会影响断路器状态 - 如果电路闭合,它将保持闭合,如果它打开,它将保持打开状态。

@FeignClient(name = "dog-service", 
             url = "http://...", 
             configuration=FeignPropagateBadRequestsConfiguration.class)
public interface DogsFeignClient {
  @RequestMapping(method = RequestMethod.POST, path = "/dogs")
  Resource<Dog> createDog(Dog dog);
}

FeignPropagateBadRequestsConfiguration在哪里

@Configuration
public class FeignSkipBadRequestsConfiguration {
    @Bean
    public ErrorDecoder errorDecoder() {
        return (methodKey, response) -> {
            int status = response.status();
            if (status == 400) {
                String body = "Bad request";
                try {
                    body = IOUtils.toString(response.body().asReader());
                } catch (Exception ignored) {}
                HttpHeaders httpHeaders = new HttpHeaders();
                response.headers().forEach((k, v) -> httpHeaders.add("feign-" + k, StringUtils.join(v,",")));
                return new FeignBadResponseWrapper(status, httpHeaders, body);
            }
            else {
                return new RuntimeException("Response Code " + status);
            }
        };
    }
}

并且FeignBadResponseWrapper

@Getter
@Setter
public class FeignBadResponseWrapper extends HystrixBadRequestException {
    private final int status;
    private final HttpHeaders headers;
    private final String body;

    public FeignBadResponseWrapper(int status, HttpHeaders headers, String body) {
        super("Bad request");
        this.status = status;
        this.headers = headers;
        this.body = body;
    }
}

这有点小技巧,您只能在 中获取响应正文ErrorDecoder,因为之后流将被关闭。但是使用它,您可以将响应数据扔给客户端代码而不影响电路:

    try {
        return dogsFeignClient.createDog(dog);
    } catch (HystrixBadRequestException he) {
        if (he instanceof FeignBadResponseWrapper) {
            // obtain data from wrapper and return it to client
        } else {
            // return basic error data for other exceptions
        }
    }
于 2017-06-20T22:34:00.720 回答