3. Spring Cloud Commons: 公共抽象

诸如服务发现、负载均衡和熔断机制之类的模式适合于公共抽象层,可以由所有 Spring Cloud 客户端使用,独立于实现(例如,使用 Eureka 或 Consul 进行发现)。

3.1 @EnableDiscoveryClient

Spring Cloud Commons 提供了 @EnableDiscoveryClient 注解。这将通过 META-INF/spring.factories 来查找 DiscoveryClient 接口的实现。发现客户端的实现在 spring.factories 的 org.springframework.cloud.client.discovery.EnableDiscoveryClient 键下添加配置类。DiscoveryClient 实现的例子包括 Spring Cloud Netflix Eureka、Spring Cloud Consul Discovery 和 Spring Cloud Zookeeper Discovery。

默认情况下,DiscoveryClient 的实现将本地 Spring Boot 服务自动注册到远程发现服务。可以通过在 @EnableDiscoveryClient 中设置 autoRegister=false 来禁用此行为。

[Note] Note

不再需要 @EnableDiscoveryClient。你可以在类路径上放置 DiscoveryClient 实现,以使 Spring Boot 应用注册到服务发现服务器。

3.1.1 健康指标

Commons 创建一个 Spring Boot HealthIndicator,DiscoveryClient 实现可以通过实现 DiscoveryHealthIndicator 参与其中。若要禁用复合 HealthIndicator,请设置 spring.cloud.discovery.client.composite-indicator.enabled=false。基于 DiscoveryClient 的通用 HealthIndicator 是自动配置的(DiscoveryClientHealthIndicator)。若要禁用它,请设置 spring.cloud.discovery.client.health-indicator.enabled=false。要禁用 DiscoveryClientHealthIndicator 的描述字段,请设置 spring.cloud.discovery.client.health-indicator.include-description=false。否则,它可以冒起作为卷起的 HealthIndicator 的 description。

3.1.2 DiscoveryClient 多实例排序

DiscoveryClient 接口继承了 Ordered。这在使用多个发现客户端时很有用,因为它允许你定义返回的 DiscoveryClient 的顺序,类似于你如何对 Spring 应用加载的 bean 进行排序。默认情况下,DiscoveryClient 的顺序设置为0。如果希望为自定义 DiscoveryClient 实现设置不同的顺序,只需重写 getOrder() 方法,以便它返回适合你的设置的值。除此之外,你可以使用属性来设置 Spring Cloud 提供的 DiscoveryClient 实现的顺序,其中包括 ConsulDiscoveryClient、EurekaDiscoveryClient 和 ZookeeperDiscoveryClient。为此,只需将 spring.cloud.{clientIdentifier}.discovery.order(或 Eureka 的 eureka.client.order)属性设置为所需的值。

3.2 服务注册

Commons 现在提供了一个 ServiceRegistry 接口,它提供了诸如 register(Registration) 和 deregister(Registration) 之类的方法,允许你提供定制的注册服务。Registration是一个标记接口。

下面的示例展示了使用中的 ServiceRegistry:

@Configuration
@EnableDiscoveryClient(autoRegister=false)
public class MyConfiguration {
    private ServiceRegistry registry;

    public MyConfiguration(ServiceRegistry registry) {
        this.registry = registry;
    }

    // called through some external process, such as an event or a custom actuator endpoint
    public void register() {
        Registration registration = constructRegistration();
        this.registry.register(registration);
    }
}

每个 ServiceRegistry 实现都有自己的 Registry 实现。

  • ZookeeperRegistration 用于 ZookeeperServiceRegistry
  • EurekaRegistration 用于 EurekaServiceRegistry
  • ConsulRegistration 用于 ConsulServiceRegistry

如果你正在使用 ServiceRegistry 接口,则需要为你正在使用的 ServiceRegistry 实现传递正确的 Registry 实现。

3.2.1 服务注册自动注册

默认情况下,ServiceRegistry 实现自动注册正在运行的服务。若要禁用该行为,可以设置:* @EnableDiscoveryClient(autoRegister=false) 永久禁用自动注册。* spring.cloud.service-registry.auto-registration.enabled=false 通过配置禁用行为。

服务注册自动注册事件

当服务自动注册时,将触发两个事件。第一个事件为 InstancePreRegisteredEvent,在注册服务之前触发。第二个事件为 InstanceRegisteredEvent,在注册服务之后触发。你可以注册 ApplicationListener(s) 来监听这些事件并做出反应。

[Note] Note

如果 spring.cloud.service-registry.auto-registration.enabled 设置为 false,则不会触发这些事件。

3.2.2 服务注册执行器端点

Spring Cloud Commons 提供了一个 /service-registry 执行器端点。此端点依赖于 Spring 应用上下文中的 Registration bean。使用 GET 调用 /service-registry 返回 Registration 状态。使用 POST 与 JSON body 到同一端点,将当前 Registration 的状态更改为新值。JSON body 必须包含具有优先值的 status 字段。请参阅更新状态和状态返回值时用于允许值的 ServiceRegistry 实现的文档。例如,Eureka 的支持状态是 UP、DOWN、OUT_OF_SERVICE 和 UNKNOWN。

3.3 Spring RestTemplate 作为负载均衡器客户端

RestTemplate 可以自动配置为使用 ribbon。要创建负载均衡的 RestTemplate,请创建一个 RestTemplate @Bean,并使用 @LoadBalanced 限定符,如下面的示例所示:

@Configuration
public class MyConfiguration {

    @LoadBalanced
    @Bean
    RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

public class MyClass {
    @Autowired
    private RestTemplate restTemplate;

    public String doOtherStuff() {
        String results = restTemplate.getForObject("http://stores/stores", String.class);
        return results;
    }
}
[Caution] 警告

RestTemplate bean 不再通过自动配置创建。单个应用程序必须创建它。

URI 需要使用虚拟主机名(即,服务名,而不是主机名)。Ribbon 客户端用于创建完整的物理地址。有关如何设置 RestTemplate 的详细信息,请参阅 RibbonAutoConfiguration。

3.4 Spring WebClient 作为负载均衡器客户端

WebClient 可以自动配置为使用 LoadBalancerClient。要创建负载均衡的 WebClient,请创建一个 WebClient.Builder @Bean 并使用 @LoadBalanced 限定符,如下面的示例所示:

@Configuration
public class MyConfiguration {

	@Bean
	@LoadBalanced
	public WebClient.Builder loadBalancedWebClientBuilder() {
		return WebClient.builder();
	}
}

public class MyClass {
    @Autowired
    private WebClient.Builder webClientBuilder;

    public Mono<String> doOtherStuff() {
        return webClientBuilder.build().get().uri("http://stores/stores")
        				.retrieve().bodyToMono(String.class);
    }
}

URI 需要使用虚拟主机名(即,服务名,而不是主机名)。Ribbon 客户端用于创建完整的物理地址。

3.4.1 重试失败请求

负载均衡的 RestTemplate 可以被配置为重试失败的请求。默认情况下,此逻辑被禁用。可以通过在应用程序的类路径中添加 Spring Retry 来启用它。负载均衡的 RestTemplate 尊重与重试失败请求相关的一些 Ribbon 配置值。可以使用 client.ribbon.MaxAutoRetries、client.ribbon.MaxAutoRetriesNextServer 和 client.ribbon.OkToRetryOnAllOperations 属性。如果希望在类路径上使用 Spring Retry 禁用重试逻辑,则可以设置 spring.cloud.loadbalancer.retry.enabled=false。有关这些属性的描述,请参阅 Ribbon 文档。

如果希望在重试中实现 BackOffPolicy,则需要创建 LoadBalancedBackOffPolicyFactory 类型的 bean,并重写 createBackOffPolicy 方法,如下面的示例所示:

@Configuration
public class MyConfiguration {
    @Bean
    LoadBalancedRetryFactory retryFactory() {
        return new LoadBalancedRetryFactory() {
            @Override
            public BackOffPolicy createBackOffPolicy(String service) {
        		return new ExponentialBackOffPolicy();
        	}
        };
    }
}
[Note] Note

前面例子中的 client 应该用 Ribbon 客户端的名字来代替。

如果希望将一个或多个 RetryListener 实现添加到重试功能,则需要创建 LoadBalancedRetryListenerFactory 类型的 bean,并返回希望用于给定服务的 RetryListener 数组,如下面的示例所示:

@Configuration
public class MyConfiguration {
    @Bean
    LoadBalancedRetryListenerFactory retryListenerFactory() {
        return new LoadBalancedRetryListenerFactory() {
            @Override
            public RetryListener[] createRetryListeners(String service) {
                return new RetryListener[]{new RetryListener() {
                    @Override
                    public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
                        //TODO Do you business...
                        return true;
                    }

                    @Override
                     public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
                        //TODO Do you business...
                    }

                    @Override
                    public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
                        //TODO Do you business...
                    }
                }};
            }
        };
    }
}

3.5 多个 RestTemplate 对象

如果你想要一个没有负载均衡的 RestTemplate,那么创建一个 RestTemplate bean 并注入它。要访问负载均衡的 RestTemplate,在创建 @Bean 时使用 @LoadBalanced 限定符,如下面的示例所示:

@Configuration
public class MyConfiguration {

    @LoadBalanced
    @Bean
    RestTemplate loadBalanced() {
        return new RestTemplate();
    }

    @Primary
    @Bean
    RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

public class MyClass {
    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    @LoadBalanced
    private RestTemplate loadBalanced;

    public String doOtherStuff() {
        return loadBalanced.getForObject("http://stores/stores", String.class);
    }

    public String doStuff() {
        return restTemplate.getForObject("http://example.com", String.class);
    }
}
[Important] Important

注意,在前面的示例中,在普通 RestTemplate 声明上使用 @Primary 注解来消除未限定的 @Autowired 注入的歧义。

[Tip] Tip

如果你看到诸如 java.lang.IllegalArgumentException:
Can not set org.springframework.web.client.RestTemplate field com.my.app.Foo.restTemplate to com.sun.proxy.$Proxy89
的错误,请尝试注入 RestOperations 或设置 spring.aop.proxyTargetClass=true。

3.6 Spring WebFlux WebClient 作为负载均衡器客户端

WebClient 可以被配置为使用 LoadBalancerClient。如果 spring-webflux 在类路径上,则可以自动配置 LoadBalancerExchangeFilterFunction。下面的示例展示如何配置 WebClient 使用负载均衡器:

public class MyClass {
    @Autowired
    private LoadBalancerExchangeFilterFunction lbFunction;

    public Mono<String> doOtherStuff() {
        return WebClient.builder().baseUrl("http://stores")
            .filter(lbFunction)
            .build()
            .get()
            .uri("/stores")
            .retrieve()
            .bodyToMono(String.class);
    }
}

URI 需要使用虚拟主机名(即,服务名称,而不是主机名)。LoadBalancerClient 用于创建一个完整的物理地址。

3.7 忽略网络接口

有时,忽略某些命名的网络接口是有用的,以便可以将它们排除在服务发现注册之外(例如,在 Docker 容器中运行时)。可以设置正则表达式的列表,以使期望的网络接口被忽略。下面的配置忽略了 docker0 接口和以 veth 开头的所有接口:

application.yml. 

spring:
  cloud:
    inetutils:
      ignoredInterfaces:
        - docker0
        - veth.*

你还可以通过使用正则表达式列表强制只使用指定的网络地址,如下面的示例所示:

bootstrap.yml

spring:
  cloud:
    inetutils:
      preferredNetworks:
        - 192.168
        - 10.0

还可以强制使用只使用站点本地地址,如下面的示例所示:

application.yml

spring:
  cloud:
    inetutils:
      useOnlySiteLocalInterfaces: true

有关站点本地地址的详细信息,请参阅 Inet4Address.html.isSiteLocalAddress()。

3.8 HTTP 客户端工厂

Spring Cloud Commons 提供了用于创建 Apache HTTP 客户端(ApacheHttpClientFactory)和 OK HTTP 客户端(OkHttpClientFactory)的 bean。只有 OK HTTP jar 在类路径上时才创建 OkHttpClientFactory bean。此外,Spring Cloud Commons 提供了用于创建两个客户端使用的连接管理器的 bean:Apache HTTP 客户端的 ApacheHttpClientConnectionManagerFactory 和 OK HTTP 客户端的 OkHttpClientConnectionPoolFactory。如果希望自定义如何在下游项目中创建 HTTP 客户端,则可以提供这些 bean 的自己实现。此外,如果提供类型为 HttpClientBuilder 或 OkHttpClient.Builder 的 bean,默认工厂使用这些构建器作为返回到下游项目的构建器的基础。还可以通过将 spring.cloud.httpclientfactories.apache.enabled 或 spring.cloud.httpclientfactories.ok.enabled 设置为 false 来禁用这些 bean 的创建。

3.9 启用特性

Spring Cloud Commons 提供了一个 /features 执行器端点。这个端点返回在类路径上可用的特性以及它们是否被启用。返回的信息包括特征类型、名称、版本和供应商。

3.9.1 特性类型

特性有两种类型:抽象和命名。

抽象特性是定义接口或抽象类的特性,以及创建的实现,如 DiscoveryClient、LoadBalancerClient 或 LockService。抽象类或接口用于在上下文中查找该类型的 bean。显示版本的是 bean.getClass().getPackage().getImplementationVersion()。

命名的特性是没有它们实现的特定类的特性,例如 "Circuit Breaker"、"API Gateway"、"Spring Cloud Bus" 等等。这些特性需要名称和 bean 类型。

3.9.2 声明特性

任何模块都可以声明任何数量的 HasFeature bean,如下面的示例所示:

@Bean
public HasFeatures commonsFeatures() {
  return HasFeatures.abstractFeatures(DiscoveryClient.class, LoadBalancerClient.class);
}

@Bean
public HasFeatures consulFeatures() {
  return HasFeatures.namedFeatures(
    new NamedFeature("Spring Cloud Bus", ConsulBusAutoConfiguration.class),
    new NamedFeature("Circuit Breaker", HystrixCommandAspect.class));
}

@Bean
HasFeatures localFeatures() {
  return HasFeatures.builder()
      .abstractFeature(Foo.class)
      .namedFeature(new NamedFeature("Bar Feature", Bar.class))
      .abstractFeature(Baz.class)
      .build();
}

这些 bean 中的每一个都应该处于一个适当的保护 @Configuration 中。

3.10 Spring Cloud 兼容性校验

由于一些用户在设置 Spring Cloud 应用程序时有问题,我们决定添加兼容性校验机制。如果当前的设置与 Spring Cloud 要求不兼容,那么它将会中断,并附上一份报告,说明到底发生了什么错误。

现在,我们校验将哪个版本的 Spring Boot 添加到类路径中。

示例报告

***************************
APPLICATION FAILED TO START
***************************

Description:

Your project setup is incompatible with our requirements due to following reasons:

- Spring Boot [2.1.0.RELEASE] is not compatible with this Spring Cloud release train


Action:

Consider applying the following actions:

- Change Spring Boot version to one of the following versions [1.2.x, 1.3.x] .
You can find the latest Spring Boot versions here [https://spring.io/projects/spring-boot#learn].
If you want to learn more about the Spring Cloud Release train compatibility, you can visit this page [https://spring.io/projects/spring-cloud#overview] and check the [Release Trains] section.

为了禁用该特性,将 spring.cloud.compatibility-verifier.enabled 设置为 false。如果要重写兼容的 Spring Boot 版本,只需设置 spring.cloud.compatibility-verifier.compatible-boot-versions 属性为逗号分隔的兼容 Spring Boot 版本列表。