你如何覆盖 OpenFeign 的 Hystrix 配置?

How do you override the Hystrix configuration for OpenFeign?

如何覆盖 OpenFeign 的 Hystrix 默认配置?大多数文档都是针对 SpringBoot + OpenFeign 的,它有自己的 Spring-特定配置覆盖系统。

理想情况下,可以为客户端配置 Hystrix 核心大小,并在每个端点的基础上配置和超时。

Hystrix OpenFeign 在构建器上有一个 setterFactory() 方法,允许您传入在设置每个目标端点时执行的 SetterFactory lambda 函数:

final SetterFactory hystrixConfigurationFactory = (target, method) -> {
    final String groupKey = target.name();
    final String commandKey = method.getAnnotation(RequestLine.class).value();

    // Configure default thread pool properties
    final HystrixThreadPoolProperties.Setter hystrixThreadPoolProperties = HystrixThreadPoolProperties.Setter()
        .withCoreSize(50)
        .withMaximumSize(200)
        .withAllowMaximumSizeToDivergeFromCoreSize(true);

    return HystrixCommand.Setter
        .withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey))
        .andCommandKey(HystrixCommandKey.Factory.asKey(commandKey))
        .andThreadPoolPropertiesDefaults(hystrixThreadPoolProperties);;
};

final MyTargetClient myTargetClient = HystrixFeign.builder()
    .setterFactory(hystrixConfigurationFactory)
    .client(new OkHttpClient())
    .encoder(new JacksonEncoder(objectMapper))
    .decoder(new JacksonDecoder(objectMapper))
    .target(new Target.HardCodedTarget<>(MyTargetClient.class, "customclientname", baseUrl))

以上示例使用 boilerplate from the OpenFeign documentation 根据目标端点函数正确命名 Hystrix 键。然后进一步配置默认线程池 属性 核心大小和最大核心大小作为所有目标函数的默认值。

但是,由于为每个目标端点调用此工厂,我们实际上可以在每个端点的基础上覆盖 Hystrix 配置。一个很好的用例是 Hystrix 超时:有时有些端点比其他端点花费的时间更长,我们需要考虑到这一点。

最简单的方法是首先创建一个注释并将其放置在需要覆盖的目标端点上:

/**
 * Override Hystrix configuration for Feign targets.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface HystrixOverride {
    int DEFAULT_EXECUTION_TIMEOUT = 2_000;

    /**
     * Execution timeout in milliseconds.
     */
    int executionTimeout() default DEFAULT_EXECUTION_TIMEOUT;
}
interface MyTargetClient {
    @HystrixOverride(executionTimeout = 10_000)
    @RequestLine("GET /rest/{storeCode}/V1/products")
    Products searchProducts(@Param("storeCode") String storeCode, @QueryMap Map<String, Object> queryMap);

    @RequestLine("GET /rest/{storeCode}/V1/products/{sku}")
    Product getProduct(@Param("storeCode") String storeCode, @Param("sku") String sku);
}

在上面的示例中,搜索 API 可能需要更长的时间才能加载,因此我们对此进行了覆盖。

不过,仅仅将覆盖注释放在目标端点函数上是不够的。我们需要回到我们的工厂并更新它以使用注释中的数据:

final SetterFactory hystrixConfigurationFactory = (target, method) -> {
    final String groupKey = target.name();
    final String commandKey = method.getAnnotation(RequestLine.class).value();

    // Configure per-function Hystrix configuration by referencing annotations
    final HystrixCommandProperties.Setter hystrixCommandProperties = HystrixCommandProperties.Setter();
    final HystrixOverride hystrixOverride = method.getAnnotation(HystrixOverride.class);

    final int executionTimeout = (hystrixOverride == null)
        ? HystrixOverride.DEFAULT_EXECUTION_TIMEOUT
        : hystrixOverride.executionTimeout();
    hystrixCommandProperties.withExecutionTimeoutInMilliseconds(executionTimeout);

    // Configure default thread pool properties
    final HystrixThreadPoolProperties.Setter hystrixThreadPoolProperties = HystrixThreadPoolProperties.Setter()
        .withCoreSize(50)
        .withMaximumSize(200)
        .withAllowMaximumSizeToDivergeFromCoreSize(true);

    return HystrixCommand.Setter
        .withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey))
        .andCommandKey(HystrixCommandKey.Factory.asKey(commandKey))
        .andCommandPropertiesDefaults(hystrixCommandProperties)
        .andThreadPoolPropertiesDefaults(hystrixThreadPoolProperties);;
};

以上检查覆盖注释是否存在,然后使用该注释中的数据为该目标端点配置执行超时。如果覆盖不存在,则将使用 HystrixOverride 端点的默认值。然后将生成的 hystrixCommandProperties 变量插入到最后的整体 HystrixCommand.Setter 中。