云原生是一种应用程序开发风格,它鼓励在持续交付和价值驱动开发领域轻松采用最佳实践。一个相关的领域是构建12 因素应用,其中开发实践与交付和运营目标保持一致——例如,通过使用声明性编程以及管理和监控。Spring Cloud 以多种特定方式促进了这些开发风格。起点是一组分布式系统中所有组件都需要轻松访问的特性。

其中许多特性由Spring Boot涵盖,Spring Cloud 在其基础上构建。Spring Cloud 还提供了另外一些特性,作为两个库:Spring Cloud Context 和 Spring Cloud Commons。Spring Cloud Context 为 Spring Cloud 应用的 ApplicationContext 提供工具和特殊服务(引导上下文、加密、刷新范围和环境端点)。Spring Cloud Commons 是一组抽象和通用类,用于不同的 Spring Cloud 实现(例如 Spring Cloud Netflix 和 Spring Cloud Consul)。

如果您因为“非法密钥大小”而遇到异常,并且使用 Sun 的 JDK,则需要安装 Java 加密扩展 (JCE) 无限制强度辖区策略文件。有关更多信息,请参阅以下链接

将文件解压到您使用的任何版本 JRE/JDK x64/x86 的 JDK/jre/lib/security 文件夹中。

Spring Cloud 在非限制性的 Apache 2.0 许可下发布。如果您想为此文档部分做出贡献或发现错误,可以在 {docslink}[github] 上找到该项目的源代码和问题跟踪器。

1. Spring Cloud Context:应用上下文服务

Spring Boot 对于如何使用 Spring 构建应用程序有其自己的看法。例如,它为常用配置文件提供了约定俗成的位置,并为常见的管理和监控任务提供了端点。Spring Cloud 在此基础上构建,并添加了一些系统中许多组件会使用或偶尔需要的特性。

1.1. 引导应用上下文

Spring Cloud 应用通过创建一个“引导”上下文来运行,该上下文是主应用程序的父上下文。此上下文负责从外部源加载配置属性以及解密本地外部配置文件中的属性。这两个上下文共享一个 Environment,这是任何 Spring 应用的外部属性源。默认情况下,引导属性(不是 bootstrap.properties,而是在引导阶段加载的属性)以高优先级添加,因此本地配置无法覆盖它们。

引导上下文使用与主应用上下文不同的约定来定位外部配置。您可以使用 bootstrap.yml 来替代 application.yml(或 .properties),以便将引导和主上下文的外部配置很好地分开。以下清单显示了一个示例

示例 1. bootstrap.yml
spring:
  application:
    name: foo
  cloud:
    config:
      uri: ${SPRING_CONFIG_URI:http://localhost:8888}

如果您的应用程序需要来自服务器的任何应用程序特定配置,最好设置 spring.application.name(在 bootstrap.ymlapplication.yml 中)。为了使属性 spring.application.name 用作应用程序的上下文 ID,您必须将其设置在 bootstrap.[properties | yml] 中。

如果要检索特定配置文件的配置,还应该在 bootstrap.[properties | yml] 中设置 spring.profiles.active

您可以通过设置 spring.cloud.bootstrap.enabled=false(例如,在系统属性中)完全禁用引导过程。

1.2. 应用上下文层次结构

如果您使用 SpringApplicationSpringApplicationBuilder 构建应用上下文,引导上下文将作为该上下文的父上下文添加。Spring 的一个特性是子上下文从其父上下文继承属性源和配置文件,因此与不使用 Spring Cloud Config 构建相同上下文相比,“主”应用上下文包含额外的属性源。额外的属性源包括

  • “bootstrap”:如果在引导上下文中找到任何 PropertySourceLocators 并且它们具有非空属性,则会出现一个优先级较高的可选 CompositePropertySource。例如,来自 Spring Cloud Config Server 的属性。有关如何定制此属性源的内容,请参阅“定制引导属性源”。

在 Spring Cloud 2022.0.3 之前,PropertySourceLocators(包括 Spring Cloud Config 的)在主应用上下文期间运行,而不是在引导上下文期间运行。您可以通过在 bootstrap.[properties | yaml] 中设置 spring.cloud.config.initialize-on-context-refresh=true 来强制 PropertySourceLocators 在引导上下文期间运行。
  • “applicationConfig: [classpath:bootstrap.yml]”(以及如果 Spring profiles 处于活动状态的相关文件):如果您有 bootstrap.yml(或 .properties),这些属性将用于配置引导上下文。然后,当设置其父上下文时,这些属性会被添加到子上下文中。它们的优先级低于 application.yml(或 .properties)以及在创建 Spring Boot 应用的正常过程中添加到子上下文的任何其他属性源。有关如何定制这些属性源的内容,请参阅“更改引导属性的位置”。

由于属性源的排序规则,“bootstrap”条目具有优先权。但是,请注意,这些条目不包含来自 bootstrap.yml 的任何数据,后者的优先级非常低,但可用于设置默认值。

您可以通过设置所创建的任何 ApplicationContext 的父上下文来扩展上下文层次结构——例如,通过使用其自己的接口或 SpringApplicationBuilder 的便捷方法(parent()child()sibling())。引导上下文是您自己创建的最顶层祖先的父级。层次结构中的每个上下文都有其自己的“引导”(可能为空)属性源,以避免意外地将父级的值传播到其后代。如果存在配置服务器,层次结构中的每个上下文原则上也可以有不同的 spring.application.name,从而有不同的远程属性源。正常的 Spring 应用上下文行为规则适用于属性解析:子上下文中的属性会按名称以及属性源名称覆盖父上下文中的属性。(如果子上下文具有与父上下文同名的属性源,则父上下文中的值不会包含在子上下文)。

请注意,SpringApplicationBuilder 允许您在整个层次结构中共享一个 Environment,但这并非默认设置。因此,特别是同级上下文不必拥有相同的配置文件或属性源,即使它们可能与父级共享公共值。

1.3. 更改引导属性的位置

bootstrap.yml(或 .properties)的位置可以通过设置 spring.cloud.bootstrap.name(默认值:bootstrap)、spring.cloud.bootstrap.location(默认值:空)或 spring.cloud.bootstrap.additional-location(默认值:空)来指定——例如,在系统属性中。

这些属性的行为类似于同名的 spring.config.* 变体。使用 spring.cloud.bootstrap.location 会替换默认位置,仅使用指定的位置。要向默认位置列表中添加位置,可以使用 spring.cloud.bootstrap.additional-location。实际上,它们通过在引导 ApplicationContextEnvironment 中设置这些属性来配置引导上下文。如果存在活动配置文件(来自 spring.profiles.active 或您正在构建的上下文中的 Environment API),该配置文件中的属性也会被加载,这与常规 Spring Boot 应用中的情况相同——例如,对于 development 配置文件,会加载 bootstrap-development.properties

1.4. 覆盖远程属性的值

引导上下文添加到您的应用程序的属性源通常是“远程的”(例如,来自 Spring Cloud Config Server)。默认情况下,它们无法在本地被覆盖。如果您想让您的应用程序使用自己的系统属性或配置文件覆盖远程属性,远程属性源必须通过设置 spring.cloud.config.allowOverride=true 来授予权限(在本地设置此项不起作用)。一旦设置了该标志,两个更精细的设置将控制远程属性相对于系统属性和应用程序本地配置的位置

  • spring.cloud.config.overrideNone=true:允许从任何本地属性源覆盖。

  • spring.cloud.config.overrideSystemProperties=false:只有系统属性、命令行参数和环境变量(而非本地配置文件)应该覆盖远程设置。

1.5. 定制引导配置

通过在 /META-INF/spring.factories 文件中,在名为 org.springframework.cloud.bootstrap.BootstrapConfiguration 的键下添加条目,可以将引导上下文配置为执行您想要的任何操作。该键的值是一个逗号分隔的 Spring @Configuration 类列表,这些类用于创建上下文。您希望在主应用上下文中可用于自动注入的任何 bean 都可以在此处创建。对于类型为 ApplicationContextInitializer@Beans 有一个特殊约定。如果您想控制启动顺序,可以使用 @Order 注解标记类(默认顺序为 last)。

添加自定义 BootstrapConfiguration 时,请注意不要意外地将添加的类通过 @ComponentScanned 扫描到您的“主”应用上下文中,那里可能不需要它们。为引导配置类使用单独的包名,并确保该包名没有被您的 @ComponentScan@SpringBootApplication 注解的配置类覆盖。

引导过程通过将初始化器注入主 SpringApplication 实例(这是正常的 Spring Boot 启动顺序,无论它是作为独立应用程序运行还是部署在应用服务器中)而结束。首先,从 spring.factories 中找到的类创建引导上下文。然后,在主 SpringApplication 启动之前,将所有类型为 ApplicationContextInitializer@Beans 添加到其中。

1.6. 定制引导属性源

引导过程添加的外部配置的默认属性源是 Spring Cloud Config Server,但您可以通过向引导上下文(通过 spring.factories)添加类型为 PropertySourceLocator 的 bean 来添加其他源。例如,您可以从不同的服务器或数据库中插入其他属性。

举例来说,考虑以下自定义定位器

@Configuration
public class CustomPropertySourceLocator implements PropertySourceLocator {

    @Override
    public PropertySource<?> locate(Environment environment) {
        return new MapPropertySource("customProperty",
                Collections.<String, Object>singletonMap("property.from.sample.custom.source", "worked as intended"));
    }

}

传入的 Environment 是即将创建的 ApplicationContext 的环境——换句话说,是我们为其提供额外属性源的环境。它已经拥有其正常的 Spring Boot 提供的属性源,因此您可以使用这些属性源来定位特定于此 Environment 的属性源(例如,通过以 spring.application.name 作为键,就像默认的 Spring Cloud Config Server 属性源定位器中那样)。

如果您创建一个包含此类的 jar,然后在其中添加一个包含以下设置的 META-INF/spring.factories 文件,则 customProperty PropertySource 将出现在任何类路径中包含该 jar 的应用程序中

org.springframework.cloud.bootstrap.BootstrapConfiguration=sample.custom.CustomPropertySourceLocator

自 Spring Cloud 2022.0.3 起,Spring Cloud 将会调用 PropertySourceLocators 两次。第一次获取将检索没有任何 profile 的属性源。这些属性源将有机会使用 spring.profiles.active 激活 profile。在主应用上下文启动后,PropertySourceLocators 将被第二次调用,这次带着任何活动的 profile,允许 PropertySourceLocators 定位任何带有 profile 的额外 PropertySources

1.7. 日志配置

如果您使用 Spring Boot 配置日志设置,并且希望它应用于所有事件,则应将此配置放在 bootstrap.[yml | properties] 中。

为了 Spring Cloud 正确初始化日志配置,您不能使用自定义前缀。例如,当初始化日志系统时,Spring Cloud 无法识别使用 custom.loggin.logpath

1.8. 环境变化

应用程序监听 EnvironmentChangeEvent 并以几种标准方式对变化做出反应(可以通过常规方式将其他 ApplicationListeners 作为 @Beans 添加)。当观察到 EnvironmentChangeEvent 时,它会包含一个已更改的键值列表,应用程序使用这些键值来

  • 重新绑定上下文中的任何 @ConfigurationProperties beans。

  • 设置 logging.level.* 中任何属性的日志级别。

请注意,Spring Cloud Config Client 默认不会轮询 Environment 中的变化。通常,我们不建议使用这种方法来检测变化(尽管您可以使用 @Scheduled 注解进行设置)。如果您有一个横向扩展的客户端应用程序,最好将 EnvironmentChangeEvent 广播给所有实例,而不是让它们轮询变化(例如,通过使用 Spring Cloud Bus)。

只要您能够实际修改 Environment 并发布事件,EnvironmentChangeEvent 就可以覆盖大量的刷新用例。请注意,这些 API 是公共的,并且是 Spring 核心的一部分)。您可以通过访问 /configprops 端点(一个标准的 Spring Boot Actuator 特性)来验证更改是否绑定到 @ConfigurationProperties beans。例如,DataSource 可以在运行时更改其 maxPoolSize(Spring Boot 创建的默认 DataSource 是一个 @ConfigurationProperties bean),并动态增加容量。重新绑定 @ConfigurationProperties 并不能覆盖另一大类用例,即您需要更多地控制刷新,并且需要更改在整个 ApplicationContext 上是原子性的。为了解决这些问题,我们引入了 @RefreshScope

1.9. 刷新范围

当配置发生变化时,标记为 @RefreshScope 的 Spring @Bean 会得到特殊处理。此功能解决了有状态 bean 的问题,这些 bean 仅在初始化时才注入其配置。例如,如果在通过 Environment 更改数据库 URL 时,某个 DataSource 具有开放连接,您可能希望这些连接的持有者能够完成正在进行的操作。然后,下一次从连接池借用连接时,将获得一个具有新 URL 的连接。

有时,甚至必须对某些只能初始化一次的 bean 应用 @RefreshScope 注解。如果 bean 是“不可变的”,您必须要么使用 @RefreshScope 注解该 bean,要么在属性键 spring.cloud.refresh.extra-refreshable 下指定类名。

如果您的 DataSource bean 是 HikariDataSource,它无法刷新。这是 spring.cloud.refresh.never-refreshable 的默认值。如果您需要刷新它,请选择不同的 DataSource 实现。

刷新范围 bean 是延迟代理,它们在使用时(即调用方法时)进行初始化,并且该范围充当已初始化值的缓存。要强制 bean 在下一次方法调用时重新初始化,必须使其缓存条目无效。

RefreshScope 是上下文中的一个 bean,它有一个公共的 refreshAll() 方法,通过清除目标缓存来刷新范围内的所有 bean。/refresh 端点(通过 HTTP 或 JMX)公开了此功能。要按名称刷新单个 bean,还有一个 refresh(String) 方法。

为了暴露 /refresh 端点,您需要向您的应用程序添加以下配置

management:
  endpoints:
    web:
      exposure:
        include: refresh
@RefreshScope@Configuration 类上(技术上)有效,但可能会导致令人惊讶的行为。例如,这并不意味着在该类中定义的所有 @Beans 本身都在 @RefreshScope 中。特别是,依赖这些 bean 的任何内容都不能指望它们在启动刷新时得到更新,除非它本身也在 @RefreshScope 中。在这种情况下,它会在刷新时重建,并重新注入其依赖项。此时,它们会从已刷新的 @Configuration 中重新初始化)。
删除一个配置值然后执行刷新不会更新该配置值的存在状态。配置属性必须存在才能在刷新后更新其值。如果您在应用程序中依赖于某个值的存在,您可能需要将您的逻辑切换为依赖于它的不存在。另一种选择是依赖于值的变化,而不是依赖于该值是否存在于应用程序的配置中。
对于 Spring AOT 转换和 Native Image,上下文刷新不受支持。对于 AOT 和 Native Image,需要将 spring.cloud.refresh.enabled 设置为 false

1.10. 加密与解密

Spring Cloud 有一个 Environment 预处理器,用于在本地解密属性值。它遵循与 Spring Cloud Config Server 相同的规则,并通过 encrypt.* 进行外部配置。因此,您可以使用 {cipher}* 形式的加密值,只要有有效密钥,它们会在主应用上下文获取 Environment 设置之前被解密。要在应用程序中使用加密功能,您需要在类路径中包含 Spring Security RSA(Maven 坐标:org.springframework.security:spring-security-rsa),并且还需要在您的 JVM 中安装完整强度的 JCE 扩展。

如果您因为“非法密钥大小”而遇到异常,并且使用 Sun 的 JDK,则需要安装 Java 加密扩展 (JCE) 无限制强度辖区策略文件。有关更多信息,请参阅以下链接

将文件解压到您使用的任何版本 JRE/JDK x64/x86 的 JDK/jre/lib/security 文件夹中。

1.11. 端点

对于 Spring Boot Actuator 应用程序,可以使用一些额外的管理端点。您可以使用

  • /actuator/env 发送 POST 请求以更新 Environment 并重新绑定 @ConfigurationProperties 和日志级别。要启用此端点,必须设置 management.endpoint.env.post.enabled=true

  • /actuator/refresh 以重新加载引导上下文并刷新 @RefreshScope bean。

  • /actuator/restart 以关闭 ApplicationContext 并重新启动它(默认禁用)。

  • /actuator/pause/actuator/resume 用于调用 ApplicationContext 上的 Lifecycle 方法(stop()start())。

虽然为 /actuator/env 端点启用 POST 方法可以为管理应用程序环境变量提供灵活性和便利,但确保端点安全和受到监控以防止潜在安全风险至关重要。添加 spring-boot-starter-security 依赖项以配置对 actuator 端点的访问控制。
如果您禁用 /actuator/restart 端点,那么 /actuator/pause/actuator/resume 端点也将被禁用,因为它们只是 /actuator/restart 的一个特例。

2. Spring Cloud Commons:通用抽象

服务发现、负载均衡和断路器等模式可以很好地抽象为一个通用层,所有 Spring Cloud 客户端都可以使用它,而与具体实现无关(例如,使用 Eureka 或 Consul 进行发现)。

2.1. @EnableDiscoveryClient 注解

Spring Cloud Commons 提供了 @EnableDiscoveryClient 注解。它会在 META-INF/spring.factories 中查找 DiscoveryClientReactiveDiscoveryClient 接口的实现。发现客户端的实现会在 spring.factories 中,在 org.springframework.cloud.client.discovery.EnableDiscoveryClient 键下添加一个配置类。DiscoveryClient 实现的例子包括 Spring Cloud Netflix EurekaSpring Cloud Consul DiscoverySpring Cloud Zookeeper Discovery

Spring Cloud 默认会提供阻塞和响应式服务发现客户端。您可以通过设置 spring.cloud.discovery.blocking.enabled=falsespring.cloud.discovery.reactive.enabled=false 轻松禁用阻塞和/或响应式客户端。要完全禁用服务发现,只需设置 spring.cloud.discovery.enabled=false

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

不再需要 @EnableDiscoveryClient 注解。您只需在类路径中放入一个 DiscoveryClient 实现,就可以使 Spring Boot 应用注册到服务发现服务器。

2.1.1. 健康指示器

Commons 会自动配置以下 Spring Boot 健康指示器。

DiscoveryClientHealthIndicator

此健康指示器基于当前注册的 DiscoveryClient 实现。

  • 要完全禁用,请设置 spring.cloud.discovery.client.health-indicator.enabled=false

  • 要禁用 description 字段,请设置 spring.cloud.discovery.client.health-indicator.include-description=false。否则,它可能会作为汇总的 HealthIndicatordescription 冒出来。

  • 要禁用服务检索,请设置 spring.cloud.discovery.client.health-indicator.use-services-query=false。默认情况下,该指示器会调用客户端的 getServices 方法。在注册服务很多的环境中,每次检查都检索所有服务可能代价过高。这会跳过服务检索,转而使用客户端的 probe 方法。

DiscoveryCompositeHealthContributor

此组合健康指示器基于所有已注册的 DiscoveryHealthIndicator bean。要禁用,请设置 spring.cloud.discovery.client.composite-indicator.enabled=false

2.1.2. 排序 DiscoveryClient 实例

DiscoveryClient 接口继承了 Ordered。这在使用多个发现客户端时非常有用,因为它允许您定义返回的发现客户端的顺序,类似于您可以对 Spring 应用程序加载的 bean 进行排序的方式。默认情况下,任何 DiscoveryClient 的顺序都设置为 0。如果您想为自定义的 DiscoveryClient 实现设置不同的顺序,只需覆盖 getOrder() 方法,使其返回适合您设置的值即可。除此之外,您可以使用属性来设置 Spring Cloud 提供的 DiscoveryClient 实现的顺序,其中包括 ConsulDiscoveryClientEurekaDiscoveryClientZookeeperDiscoveryClient。为此,只需将 spring.cloud.{clientIdentifier}.discovery.order(对于 Eureka 则是 eureka.client.order)属性设置为期望的值即可。

2.1.3. SimpleDiscoveryClient

如果类路径中没有 Service-Registry 支持的 DiscoveryClient,将使用 SimpleDiscoveryClient 实例,它使用属性获取服务和实例的信息。

可用实例的信息应通过以下格式的属性传递:spring.cloud.discovery.client.simple.instances.service1[0].uri=http://s11:8080,其中 spring.cloud.discovery.client.simple.instances 是通用前缀,然后 service1 代表所讨论服务的 ID,而 [0] 表示实例的索引号(如示例所示,索引从 0 开始),最后 uri 的值是实例可用的实际 URI。

2.2. ServiceRegistry

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 实现。

  • ZookeeperRegistrationZookeeperServiceRegistry 一起使用

  • EurekaRegistrationEurekaServiceRegistry 一起使用

  • ConsulRegistrationConsulServiceRegistry 一起使用

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

2.2.1. ServiceRegistry 自动注册

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

ServiceRegistry 自动注册事件

服务自动注册时会触发两个事件。第一个事件称为 InstancePreRegisteredEvent,在服务注册之前触发。第二个事件称为 InstanceRegisteredEvent,在服务注册之后触发。您可以注册一个或多个 ApplicationListener 来监听并响应这些事件。

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

2.2.2. 服务注册中心 Actuator Endpoint

Spring Cloud Commons 提供了一个 /serviceregistry actuator endpoint。这个 endpoint 依赖于 Spring Application Context 中的一个 Registration bean。使用 GET 调用 /serviceregistry 会返回 Registration 的状态。使用 POST 并附带 JSON body 调用同一个 endpoint 会将当前 Registration 的状态更改为新值。JSON body 必须包含 status 字段及首选值。关于更新状态时的允许值以及状态返回的值,请参阅您使用的 ServiceRegistry 实现的文档。例如,Eureka 支持的状态有 UP, DOWN, OUT_OF_SERVICEUNKNOWN

2.3. Spring RestTemplate 作为负载均衡客户端

您可以将 RestTemplate 配置为使用负载均衡客户端。要创建一个负载均衡的 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;
    }
}
RestTemplate bean 不再通过自动配置创建。各个应用程序必须自行创建它。

URI 需要使用虚拟主机名(即服务名,而非主机名)。BlockingLoadBalancerClient 用于创建完整的物理地址。

要使用负载均衡的 RestTemplate,您的 classpath 中需要有负载均衡实现。将 Spring Cloud LoadBalancer starter 添加到您的项目中使用它。

2.4. Spring WebClient 作为负载均衡客户端

您可以将 WebClient 配置为自动使用负载均衡客户端。要创建一个负载均衡的 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 需要使用虚拟主机名(即服务名,而非主机名)。Spring Cloud LoadBalancer 用于创建完整的物理地址。

如果您想使用 @LoadBalanced WebClient.Builder,您的 classpath 中需要有负载均衡实现。我们建议您将 Spring Cloud LoadBalancer starter 添加到您的项目。然后,底层会使用 ReactiveLoadBalancer

2.4.1. 重试失败的请求

负载均衡的 RestTemplate 可以配置为重试失败的请求。默认情况下,此逻辑是禁用的。对于非响应式版本(使用 RestTemplate),您可以通过将 Spring Retry 添加到应用程序的 classpath 来启用它。对于响应式版本(使用 WebTestClient),您需要设置 spring.cloud.loadbalancer.retry.enabled=true

如果您想在 classpath 中存在 Spring Retry 或 Reactive Retry 的情况下禁用重试逻辑,可以设置 spring.cloud.loadbalancer.retry.enabled=false

对于非响应式实现,如果您想在重试中实现 BackOffPolicy,需要创建一个 LoadBalancedRetryFactory 类型的 bean 并覆盖 createBackOffPolicy() 方法。

对于响应式实现,只需将 spring.cloud.loadbalancer.retry.backoff.enabled 设置为 false 即可启用。(*译注:此处原文为false,根据通常配置习惯,true应为启用。请参考官方最新文档或自行测试确认*)

您可以设置

  • spring.cloud.loadbalancer.retry.maxRetriesOnSameServiceInstance - 表示在同一个 ServiceInstance 上应重试请求的次数(对每个选定的实例单独计数)

  • spring.cloud.loadbalancer.retry.maxRetriesOnNextServiceInstance - 表示在新选择的 ServiceInstance 上应重试请求的次数

  • spring.cloud.loadbalancer.retry.retryableStatusCodes - 始终重试失败请求的状态码。

对于响应式实现,您还可以额外设置: - spring.cloud.loadbalancer.retry.backoff.minBackoff - 设置最小退避时长(默认为 5 毫秒) - spring.cloud.loadbalancer.retry.backoff.maxBackoff - 设置最大退避时长(默认为毫秒的最大 long 值) - spring.cloud.loadbalancer.retry.backoff.jitter - 设置用于计算每次调用的实际退避时长的抖动因子(默认为 0.5)。

对于响应式实现,您还可以实现自己的 LoadBalancerRetryPolicy,以更详细地控制负载均衡调用的重试。

对于这两种实现,您还可以通过在 spring.cloud.loadbalancer.[serviceId].retry.retryable-exceptions 属性下添加值列表来设置触发重试的异常。如果您这样做,我们会确保将 RetryableStatusCodeExceptions 添加到您提供的异常列表中,以便我们也对可重试的状态码进行重试。如果您未通过属性指定任何异常,我们默认使用的异常是 IOExceptionTimeoutExceptionRetryableStatusCodeException。您还可以通过将 spring.cloud.loadbalancer.[serviceId].retry.retry-on-all-exceptions 设置为 true 来启用对所有异常的重试。

如果您将阻塞实现与 Spring Retries 一起使用,并且希望保留先前版本的行为,请将 spring.cloud.loadbalancer.[serviceId].retry.retry-on-all-exceptions 设置为 true,因为这曾经是阻塞实现的默认模式。
可以使用与上述相同的属性单独配置各个 Loadbalancer 客户端,只是前缀变为 spring.cloud.loadbalancer.clients.<clientId>.*,其中 clientId 是负载均衡器的名称。
对于负载均衡重试,默认情况下,我们会使用 RetryAwareServiceInstanceListSupplier 包装 ServiceInstanceListSupplier bean,以便在可用时选择与之前选择的实例不同的实例。您可以通过将 spring.cloud.loadbalancer.retry.avoidPreviousInstance 的值设置为 false 来禁用此行为。
@Configuration
public class MyConfiguration {
    @Bean
    LoadBalancedRetryFactory retryFactory() {
        return new LoadBalancedRetryFactory() {
            @Override
            public BackOffPolicy createBackOffPolicy(String service) {
                return new ExponentialBackOffPolicy();
            }
        };
    }
}

如果您想为重试功能添加一个或多个 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...
                    }
                }};
            }
        };
    }
}

2.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);
    }
}
注意前例中在普通的 RestTemplate 声明上使用了 @Primary 注解,以消除非限定符 @Autowired 注入的歧义。
如果您看到诸如 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

2.6. 多个 WebClient 对象

如果您想要一个非负载均衡的 WebClient,请创建一个 WebClient bean 并注入它。要访问负载均衡的 WebClient,请在创建您的 @Bean 时使用 @LoadBalanced 限定符,如下例所示:

@Configuration
public class MyConfiguration {

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

    @Primary
    @Bean
    WebClient.Builder webClient() {
        return WebClient.builder();
    }
}

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

    @Autowired
    @LoadBalanced
    private WebClient.Builder loadBalanced;

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

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

2.7. Spring WebFlux WebClient 作为负载均衡客户端

Spring WebFlux 可以与响应式和非响应式 WebClient 配置一起使用,具体如下文所述:

2.7.1. 使用 ReactorLoadBalancerExchangeFilterFunction 的 Spring WebFlux WebClient

您可以将 WebClient 配置为使用 ReactiveLoadBalancer。如果您将 Spring Cloud LoadBalancer starter 添加到项目中并且 spring-webflux 在 classpath 中,则 ReactorLoadBalancerExchangeFilterFunction 会自动配置。以下示例展示了如何配置 WebClient 使用响应式负载均衡器:

public class MyClass {
    @Autowired
    private ReactorLoadBalancerExchangeFilterFunction lbFunction;

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

URI 需要使用虚拟主机名(即服务名,而非主机名)。ReactorLoadBalancer 用于创建完整的物理地址。

2.7.2. 使用非响应式负载均衡客户端的 Spring WebFlux WebClient

如果 spring-webflux 在 classpath 中,则 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 用于创建完整的物理地址。

警告:此方法现已弃用。我们建议您改用 WebFlux 与响应式负载均衡器

2.8. 忽略网络接口

有时,忽略某些指定的网络接口很有用,以便将它们从服务发现注册中排除(例如,在 Docker 容器中运行时)。可以设置一个正则表达式列表,以使期望的网络接口被忽略。以下配置会忽略 docker0 接口以及所有以 veth 开头的接口:

示例 2. application.yml
spring:
  cloud:
    inetutils:
      ignoredInterfaces:
        - docker0
        - veth.*

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

示例 3. bootstrap.yml
spring:
  cloud:
    inetutils:
      preferredNetworks:
        - 192.168
        - 10.0

您也可以强制只使用站点本地地址,如下例所示:

示例 4. application.yml
spring:
  cloud:
    inetutils:
      useOnlySiteLocalInterfaces: true

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

2.9. HTTP 客户端工厂

Spring Cloud Commons 提供了用于创建 Apache HTTP 客户端(ApacheHttpClientFactory)和 OK HTTP 客户端(OkHttpClientFactory)的 bean。只有当 OK HTTP jar 在 classpath 中时,才会创建 OkHttpClientFactory bean。此外,Spring Cloud Commons 还提供了用于创建这两种客户端使用的连接管理器的 bean:用于 Apache HTTP 客户端的 ApacheHttpClientConnectionManagerFactory 和用于 OK HTTP 客户端的 OkHttpClientConnectionPoolFactory。如果您想自定义下游项目中 HTTP 客户端的创建方式,可以提供这些 bean 的自己的实现。此外,如果您提供 HttpClientBuilderOkHttpClient.Builder 类型的 bean,默认工厂将使用这些 builder 作为返回给下游项目的 builder 的基础。您还可以通过将 spring.cloud.httpclientfactories.apache.enabledspring.cloud.httpclientfactories.ok.enabled 设置为 false 来禁用这些 bean 的创建。

2.10. 已启用特性

Spring Cloud Commons 提供了一个 /features actuator endpoint。这个 endpoint 返回 classpath 中可用的特性以及它们是否已启用。返回的信息包括特性类型、名称、版本和供应商。

2.10.1. 特性类型

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

抽象特性是指定义了接口或抽象类,并且由实现创建的特性,例如 DiscoveryClientLoadBalancerClientLockService。抽象类或接口用于在上下文中查找该类型的 bean。显示的版本是 bean.getClass().getPackage().getImplementationVersion()

命名特性是指没有特定实现类的特性。这些特性包括“Circuit Breaker”、“API Gateway”、“Spring Cloud Bus”等。这些特性需要一个名称和一个 bean 类型。

2.10.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(Something.class)
      .namedFeature(new NamedFeature("Some Other Feature", Someother.class))
      .abstractFeature(Somethingelse.class)
      .build();
}

这些 bean 都应该放在适当防护的 @Configuration 中。

2.11. Spring Cloud 兼容性验证

考虑到一些用户在设置 Spring Cloud 应用程序时遇到问题,我们决定添加一个兼容性验证机制。如果您当前的设置与 Spring Cloud 要求不兼容,它将中断并提供一份报告,显示具体哪里出了问题。

目前我们验证您的 classpath 中添加了哪个版本的 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://springjava.cn/projects/spring-boot#learn].
If you want to learn more about the Spring Cloud Release train compatibility, you can visit this page [https://springjava.cn/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 版本列表。

3. Spring Cloud LoadBalancer

Spring Cloud 提供了自己的客户端负载均衡器抽象和实现。对于负载均衡机制,已添加 ReactiveLoadBalancer 接口,并为其提供了**轮询(Round-Robin)**和**随机(Random)**实现。为了获取可供选择的实例,使用了响应式 ServiceInstanceListSupplier。目前我们支持基于服务发现的 ServiceInstanceListSupplier 实现,它使用 classpath 中可用的 Discovery Client 从服务发现获取可用实例。

可以通过将 spring.cloud.loadbalancer.enabled 的值设置为 false 来禁用 Spring Cloud LoadBalancer。

3.1. 负载均衡器上下文的预加载

Spring Cloud LoadBalancer 为每个服务 ID 创建一个单独的 Spring 子上下文。默认情况下,这些上下文是延迟初始化的,即在对某个服务 ID 的第一个请求进行负载均衡时才初始化。

您可以选择预加载这些上下文。为此,请使用 spring.cloud-loadbalancer.eager-load.clients 属性指定您希望进行预加载的服务 ID。

3.2. 切换负载均衡算法

默认使用的 ReactiveLoadBalancer 实现是 RoundRobinLoadBalancer。要切换到不同的实现,无论是针对选定的服务还是所有服务,您都可以使用自定义负载均衡器配置机制

例如,可以通过 @LoadBalancerClient 注解传递以下配置来切换到使用 RandomLoadBalancer

public class CustomLoadBalancerConfiguration {

    @Bean
    ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
            LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new RandomLoadBalancer(loadBalancerClientFactory
                .getLazyProvider(name, ServiceInstanceListSupplier.class),
                name);
    }
}
您作为 @LoadBalancerClient@LoadBalancerClients 配置参数传递的类要么不应使用 @Configuration 注解,要么应在组件扫描范围之外。

3.3. Spring Cloud LoadBalancer 集成

为了方便使用 Spring Cloud LoadBalancer,我们提供了可与 WebClient 一起使用的 ReactorLoadBalancerExchangeFilterFunction,以及可与 RestTemplate 一起使用的 BlockingLoadBalancerClient。您可以在以下部分查看更多信息和使用示例:

3.4. Spring Cloud LoadBalancer 缓存

除了每次选择实例时通过 DiscoveryClient 获取实例的基本 ServiceInstanceListSupplier 实现外,我们还提供了两种缓存实现。

3.4.1. 基于 Caffeine 的负载均衡器缓存实现

如果您的 classpath 中有 com.github.ben-manes.caffeine:caffeine,将使用基于 Caffeine 的实现。有关如何配置它,请参阅LoadBalancerCacheConfiguration 部分。

如果您使用 Caffeine,还可以通过在 spring.cloud.loadbalancer.cache.caffeine.spec 属性中传入自己的 Caffeine Specification 来覆盖负载均衡器的默认 Caffeine 缓存设置。

警告:传入您自己的 Caffeine Specification 将覆盖任何其他 LoadBalancerCache 设置,包括General LoadBalancer Cache Configuration 字段,例如 ttlcapacity

3.4.2. 默认负载均衡器缓存实现

如果您的 classpath 中没有 Caffeine,将使用 DefaultLoadBalancerCache,它随 spring-cloud-starter-loadbalancer 自动提供。有关如何配置它,请参阅LoadBalancerCacheConfiguration 部分。

要使用 Caffeine 而非默认缓存,请将 com.github.ben-manes.caffeine:caffeine 依赖项添加到 classpath。

3.4.3. 负载均衡器缓存配置

您可以设置自己的 ttl 值(写入后条目应过期的时长),表示为 Duration,通过将符合 Spring Boot String to Duration 转换器语法String 作为 spring.cloud.loadbalancer.cache.ttl 属性的值传递。您还可以通过设置 spring.cloud.loadbalancer.cache.capacity 属性的值来设置自己的负载均衡器缓存初始容量。

默认设置包括 ttl 设置为 35 秒,默认 initialCapacity256

您还可以通过将 spring.cloud.loadbalancer.cache.enabled 的值设置为 false 来完全禁用负载均衡器缓存。

尽管基本的非缓存实现对于原型设计和测试很有用,但其效率远低于缓存版本,因此我们建议在生产环境中始终使用缓存版本。如果缓存已由 DiscoveryClient 实现完成,例如 EurekaDiscoveryClient,则应禁用负载均衡器缓存以防止双重缓存。
当您创建自己的配置时,如果您使用 CachingServiceInstanceListSupplier,请确保将其直接放在通过网络获取实例的 supplier 之后,例如 DiscoveryClientServiceInstanceListSupplier,在任何其他过滤 supplier 之前。

3.5. 加权负载均衡

为了启用加权负载均衡,我们提供了 WeightedServiceInstanceListSupplier。我们使用 WeightFunction 来计算每个实例的权重。默认情况下,我们尝试从元数据映射中读取并解析权重(键为 weight)。

如果元数据映射中未指定权重,我们将此实例的权重默认为 1。

您可以通过将 spring.cloud.loadbalancer.configurations 的值设置为 weighted,或者通过提供自己的 ServiceInstanceListSupplier bean 来配置它,例如:

public class CustomLoadBalancerConfiguration {

    @Bean
    public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
            ConfigurableApplicationContext context) {
        return ServiceInstanceListSupplier.builder()
                    .withDiscoveryClient()
                    .withWeighted()
                    .withCaching()
                    .build(context);
    }
}
您还可以通过提供 WeightFunction 来自定义权重计算逻辑。

您可以使用此示例配置使所有实例具有随机权重:

public class CustomLoadBalancerConfiguration {

    @Bean
    public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
            ConfigurableApplicationContext context) {
        return ServiceInstanceListSupplier.builder()
                    .withDiscoveryClient()
                    .withWeighted(instance -> ThreadLocalRandom.current().nextInt(1, 101))
                    .withCaching()
                    .build(context);
    }
}

3.6. 基于区域的负载均衡

为了启用基于区域的负载均衡,我们提供了 ZonePreferenceServiceInstanceListSupplier。我们使用 DiscoveryClient 特定的 zone 配置(例如 eureka.instance.metadata-map.zone)来选择客户端尝试筛选可用服务实例的区域。

您还可以通过设置 spring.cloud.loadbalancer.zone 属性的值来覆盖 DiscoveryClient 特定的区域设置。
目前,只有 Eureka Discovery Client 实现了设置负载均衡器区域的功能。对于其他服务发现客户端,请设置 spring.cloud.loadbalancer.zone 属性。更多实现即将推出。
为了确定获取到的 ServiceInstance 的区域,我们检查其元数据映射中 `"zone"` 键下的值。

ZonePreferenceServiceInstanceListSupplier 会过滤获取到的实例,并只返回同一区域内的实例。如果区域为 null 或同一区域内没有实例,它会返回所有获取到的实例。

为了使用基于区域的负载均衡方法,您需要在自定义配置中实例化一个 ZonePreferenceServiceInstanceListSupplier bean。

我们使用委托(delegate)来处理 ServiceInstanceListSupplier bean。我们建议使用 DiscoveryClientServiceInstanceListSupplier 委托,用 CachingServiceInstanceListSupplier 包装它以利用负载均衡器缓存机制,然后将生成的 bean 传递给 ZonePreferenceServiceInstanceListSupplier 的构造函数。

您可以使用此示例配置进行设置:

public class CustomLoadBalancerConfiguration {

    @Bean
    public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
            ConfigurableApplicationContext context) {
        return ServiceInstanceListSupplier.builder()
                    .withDiscoveryClient()
                    .withCaching()
                    .withZonePreference()
                    .build(context);
    }
}

3.7. 负载均衡器的实例健康检查

可以为负载均衡器启用定时健康检查。为此提供了 HealthCheckServiceInstanceListSupplier。它会定期验证委托 ServiceInstanceListSupplier 提供的实例是否仍然存活,并仅返回健康的实例,除非没有健康的实例 - 此时它会返回所有获取到的实例。

当使用 SimpleDiscoveryClient 时,此机制特别有用。对于由实际服务注册中心支持的客户端,不必使用此机制,因为查询外部服务发现后我们已经获得了健康的实例。
对于每个服务实例数量较少的设置,也推荐使用此 supplier,以避免在失败的实例上重试调用。
如果使用任何由服务发现支持的 supplier,通常不必添加此健康检查机制,因为我们直接从服务注册中心获取实例的健康状态。
HealthCheckServiceInstanceListSupplier 依赖于委托 flux 提供的更新实例。在少数情况下,当您想使用一个不刷新实例的委托,即使实例列表可能发生变化(例如我们提供的 DiscoveryClientServiceInstanceListSupplier),您可以将 spring.cloud.loadbalancer.health-check.refetch-instances 设置为 true,以让 HealthCheckServiceInstanceListSupplier 刷新实例列表。然后,您还可以通过修改 spring.cloud.loadbalancer.health-check.refetch-instances-interval 的值来调整刷新间隔,并通过将 spring.cloud.loadbalancer.health-check.repeat-health-check 设置为 false 来选择禁用额外的健康检查重复,因为每次实例刷新也会触发一次健康检查。

HealthCheckServiceInstanceListSupplier 使用以 spring.cloud.loadbalancer.health-check 为前缀的属性。您可以设置调度器的 initialDelayinterval。您可以通过设置 spring.cloud.loadbalancer.health-check.path.default 属性的值来设置健康检查 URL 的默认路径。您还可以通过设置 spring.cloud.loadbalancer.health-check.path.[SERVICE_ID] 属性的值来为任何给定服务设置特定值,将 [SERVICE_ID] 替换为您的服务的正确 ID。如果未指定 [SERVICE_ID],则默认使用 /actuator/health。如果 [SERVICE_ID] 的值设置为 null 或空,则不会执行健康检查。您还可以通过设置 spring.cloud.loadbalancer.health-check.port 的值来为健康检查请求设置自定义端口。如果未设置端口,则使用服务实例上请求的服务可用的端口。

如果您依赖默认路径(/actuator/health),请确保将 spring-boot-starter-actuator 添加到您的依赖项中,除非您计划自己添加这样一个 endpoint。
默认情况下,healthCheckFlux 会在获取到的每个存活的 ServiceInstance 上发出(emit)。您可以通过将 spring.cloud.loadbalancer.health-check.update-results-list 的值设置为 false 来修改此行为。如果此属性设置为 false,整个存活实例序列将首先收集到一个列表中,然后才发出(emit),这确保了 flux 不会在属性中设置的健康检查间隔之间发出值。

为了使用健康检查调度器方法,您需要在自定义配置中实例化一个 HealthCheckServiceInstanceListSupplier bean。

我们使用委托来处理 ServiceInstanceListSupplier bean。我们建议在 HealthCheckServiceInstanceListSupplier 的构造函数中传递一个 DiscoveryClientServiceInstanceListSupplier 委托。

您可以使用此示例配置进行设置:

public class CustomLoadBalancerConfiguration {

    @Bean
    public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
            ConfigurableApplicationContext context) {
        return ServiceInstanceListSupplier.builder()
                    .withDiscoveryClient()
                    .withHealthChecks()
                    .build(context);
        }
    }
对于非响应式栈,使用 withBlockingHealthChecks() 方法创建此 supplier。您还可以传入自己的 WebClientRestTemplate 实例用于检查。
HealthCheckServiceInstanceListSupplier 有其基于 Reactor Flux replay() 的自身缓存机制。因此,如果正在使用它,您可能希望跳过使用 CachingServiceInstanceListSupplier 包装该 supplier。
当您创建自己的配置时,HealthCheckServiceInstanceListSupplier,请确保将其直接放在通过网络获取实例的 supplier 之后,例如 DiscoveryClientServiceInstanceListSupplier,在任何其他过滤 supplier 之前。

3.8. 负载均衡器优先选择同一实例

您可以将负载均衡器设置为优先选择之前选定的实例(如果该实例可用)。

为此,您需要使用 SameInstancePreferenceServiceInstanceListSupplier。您可以通过将 spring.cloud.loadbalancer.configurations 的值设置为 same-instance-preference,或者通过提供自己的 ServiceInstanceListSupplier bean 来配置它,例如:

public class CustomLoadBalancerConfiguration {

    @Bean
    public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
            ConfigurableApplicationContext context) {
        return ServiceInstanceListSupplier.builder()
                    .withDiscoveryClient()
                    .withSameInstancePreference()
                    .build(context);
        }
    }
这也是 Zookeeper StickyRule 的替代方案。

3.9. 负载均衡器的基于请求的粘性会话

您可以将负载均衡器设置为优先选择请求 cookie 中提供了 instanceId 的实例。如果请求通过 ClientRequestContextServerHttpRequestContext 传递给负载均衡器,我们当前支持此功能,这些上下文被 SC LoadBalancer 的 exchange filter 函数和 filter 使用。

为此,您需要使用 RequestBasedStickySessionServiceInstanceListSupplier。您可以通过将 spring.cloud.loadbalancer.configurations 的值设置为 request-based-sticky-session,或者通过提供自己的 ServiceInstanceListSupplier bean 来配置它,例如:

public class CustomLoadBalancerConfiguration {

    @Bean
    public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
            ConfigurableApplicationContext context) {
        return ServiceInstanceListSupplier.builder()
                    .withDiscoveryClient()
                    .withRequestBasedStickySession()
                    .build(context);
        }
    }

对于该功能,在转发请求之前更新选定的服务实例(如果原始请求 cookie 中的实例不可用,则选定的实例可能不同)会很有用。为此,请将 spring.cloud.loadbalancer.sticky-session.add-service-instance-cookie 的值设置为 true

默认情况下,cookie 的名称是 sc-lb-instance-id。您可以通过更改 spring.cloud.loadbalancer.instance-id-cookie-name 属性的值来修改它。

此特性目前支持基于 WebClient 的负载均衡。

3.10. Spring Cloud LoadBalancer 提示

Spring Cloud LoadBalancer 允许您设置 String 类型的提示,这些提示在 Request 对象中传递给负载均衡器,并且可以在能够处理它们的 ReactiveLoadBalancer 实现中使用。

您可以通过设置 spring.cloud.loadbalancer.hint.default 属性的值来为所有服务设置默认提示。您还可以通过设置 spring.cloud.loadbalancer.hint.[SERVICE_ID] 属性的值来为任何给定服务设置特定值,将 [SERVICE_ID] 替换为您的服务的正确 ID。如果用户未设置提示,则使用 default

3.11. 基于提示的负载均衡

我们还提供了 HintBasedServiceInstanceListSupplier,它是用于基于提示的实例选择的 ServiceInstanceListSupplier 实现。

HintBasedServiceInstanceListSupplier 检查提示请求头(默认头名称为 X-SC-LB-Hint,但你可以通过修改 spring.cloud.loadbalancer.hint-header-name 属性的值来修改它),如果找到提示请求头,则使用请求头中传递的提示值来过滤服务实例。

如果未添加提示请求头,HintBasedServiceInstanceListSupplier 则使用属性中的提示值来过滤服务实例。

如果没有通过请求头或属性设置任何提示,则返回由代理提供的所有服务实例。

在过滤时,HintBasedServiceInstanceListSupplier 会查找在其 metadataMaphint 键下设置了匹配值的服务实例。如果没有找到匹配的实例,则返回由代理提供的所有实例。

你可以使用以下示例配置来设置它

public class CustomLoadBalancerConfiguration {

    @Bean
    public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
            ConfigurableApplicationContext context) {
        return ServiceInstanceListSupplier.builder()
                    .withDiscoveryClient()
                    .withCaching()
                    .withHints()
                    .build(context);
    }
}

3.12. 转换负载均衡的 HTTP 请求

你可以使用选定的 ServiceInstance 来转换负载均衡的 HTTP 请求。

对于 RestTemplate,你需要实现并定义 LoadBalancerRequestTransformer,如下所示

@Bean
public LoadBalancerRequestTransformer transformer() {
    return new LoadBalancerRequestTransformer() {
        @Override
        public HttpRequest transformRequest(HttpRequest request, ServiceInstance instance) {
            return new HttpRequestWrapper(request) {
                @Override
                public HttpHeaders getHeaders() {
                    HttpHeaders headers = new HttpHeaders();
                    headers.putAll(super.getHeaders());
                    headers.add("X-InstanceId", instance.getInstanceId());
                    return headers;
                }
            };
        }
    };
}

对于 WebClient,你需要实现并定义 LoadBalancerClientRequestTransformer,如下所示

@Bean
public LoadBalancerClientRequestTransformer transformer() {
    return new LoadBalancerClientRequestTransformer() {
        @Override
        public ClientRequest transformRequest(ClientRequest request, ServiceInstance instance) {
            return ClientRequest.from(request)
                    .header("X-InstanceId", instance.getInstanceId())
                    .build();
        }
    };
}

如果定义了多个转换器,它们将按照 Bean 定义的顺序应用。或者,你可以使用 LoadBalancerRequestTransformer.DEFAULT_ORDERLoadBalancerClientRequestTransformer.DEFAULT_ORDER 来指定顺序。

3.13. Spring Cloud LoadBalancer Starter

我们还提供了一个 starter,让你可以在 Spring Boot 应用程序中轻松添加 Spring Cloud LoadBalancer。要使用它,只需将 org.springframework.cloud:spring-cloud-starter-loadbalancer 添加到构建文件中的 Spring Cloud 依赖项中。

Spring Cloud LoadBalancer starter 包含 Spring Boot CachingEvictor

3.14. 传递你自己的 Spring Cloud LoadBalancer 配置

你也可以使用 @LoadBalancerClient 注解传递你自己的负载均衡客户端配置,如下所示,传递负载均衡客户端的名称和配置类

@Configuration
@LoadBalancerClient(value = "stores", configuration = CustomLoadBalancerConfiguration.class)
public class MyConfiguration {

    @Bean
    @LoadBalanced
    public WebClient.Builder loadBalancedWebClientBuilder() {
        return WebClient.builder();
    }
}
为了让你更轻松地处理自己的 LoadBalancer 配置,我们在 ServiceInstanceListSupplier 类中添加了一个 builder() 方法。
你还可以使用我们的替代预定义配置来代替默认配置,方法是将 spring.cloud.loadbalancer.configurations 属性的值设置为 zone-preference 以使用带缓存的 ZonePreferenceServiceInstanceListSupplier,或设置为 health-check 以使用带缓存的 HealthCheckServiceInstanceListSupplier

你可以使用此功能实例化 ServiceInstanceListSupplierReactorLoadBalancer 的不同实现,这些实现可以是你自己编写的,也可以是我们提供的替代方案(例如 ZonePreferenceServiceInstanceListSupplier),以覆盖默认设置。

你可以在这里看到一个自定义配置的示例。

注解的 value 参数(上例中的 stores)指定了我们应该将请求发送到的服务 ID,使用给定的自定义配置。

你还可以通过 @LoadBalancerClients 注解传递多个配置(用于多个负载均衡客户端),如下例所示

@Configuration
@LoadBalancerClients({@LoadBalancerClient(value = "stores", configuration = StoresLoadBalancerClientConfiguration.class), @LoadBalancerClient(value = "customers", configuration = CustomersLoadBalancerClientConfiguration.class)})
public class MyConfiguration {

    @Bean
    @LoadBalanced
    public WebClient.Builder loadBalancedWebClientBuilder() {
        return WebClient.builder();
    }
}
您作为 @LoadBalancerClient@LoadBalancerClients 配置参数传递的类要么不应使用 @Configuration 注解,要么应在组件扫描范围之外。
创建自己的配置时,如果使用 CachingServiceInstanceListSupplierHealthCheckServiceInstanceListSupplier,请确保只使用其中一个,而不是两者都用,并且确保将其放置在从网络检索实例的供应商(例如 DiscoveryClientServiceInstanceListSupplier)之后,位于任何其他过滤供应商之前。

3.15. Spring Cloud LoadBalancer 生命周期

使用自定义 LoadBalancer 配置注册的一种可能有用的 bean 类型是 LoadBalancerLifecycle

LoadBalancerLifecycle bean 提供回调方法,命名为 onStart(Request<RC> request)onStartRequest(Request<RC> request, Response<T> lbResponse)onComplete(CompletionContext<RES, T, RC> completionContext),你应该实现这些方法来指定负载均衡之前和之后应执行的操作。

onStart(Request<RC> request) 接受一个 Request 对象作为参数。它包含用于选择合适实例的数据,包括下游客户端请求和提示onStartRequest 也接受 Request 对象,此外还接受 Response<T> 对象作为参数。另一方面,向 onComplete(CompletionContext<RES, T, RC> completionContext) 方法提供一个 CompletionContext 对象。它包含 LoadBalancer 响应,包括选定的服务实例,对该服务实例执行的请求的 Status,以及(如果可用)返回给下游客户端的响应,以及(如果发生异常)相应的 Throwable

supports(Class requestContextClass, Class responseClass, Class serverTypeClass) 方法可用于确定相关处理器是否处理提供的类型的对象。如果用户未覆盖此方法,它将返回 true

在前面的方法调用中,RC 表示 RequestContext 类型,RES 表示客户端响应类型,T 表示返回的服务端类型。

3.16. Spring Cloud LoadBalancer 统计信息

我们提供了一个名为 MicrometerStatsLoadBalancerLifecycleLoadBalancerLifecycle bean,它使用 Micrometer 为负载均衡调用提供统计信息。

为了将此 bean 添加到你的应用程序上下文中,将 spring.cloud.loadbalancer.stats.micrometer.enabled 的值设置为 true 并确保有一个 MeterRegistry 可用(例如,通过向你的项目添加Spring Boot Actuator)。

MicrometerStatsLoadBalancerLifecycleMeterRegistry 中注册以下度量指标 (meters)

  • loadbalancer.requests.active: 一个计量器 (gauge),允许你监控任何服务实例当前活跃请求的数量(服务实例数据可通过标签获取);

  • loadbalancer.requests.success: 一个计时器 (timer),测量将响应传递给底层客户端的负载均衡请求的执行时间;

  • loadbalancer.requests.failed: 一个计时器 (timer),测量以异常结束的负载均衡请求的执行时间;

  • loadbalancer.requests.discard: 一个计数器 (counter),测量被丢弃的负载均衡请求的数量,即 LoadBalancer 未能获取到用于运行请求的服务实例的请求。

服务实例、请求数据和响应数据的附加信息在可用时通过标签添加到指标中。

对于某些实现,例如 BlockingLoadBalancerClient,请求和响应数据可能不可用,因为我们从参数中确定通用类型,并且可能无法确定类型和读取数据。
当为给定度量器添加至少一条记录时,度量器会在注册表中注册。
你可以通过添加 MeterFilters 来进一步配置这些指标的行为(例如,添加发布百分位和直方图)。

3.17. 配置单个 LoadBalancerClient

单个 LoadBalancer 客户端可以使用不同的前缀 spring.cloud.loadbalancer.clients.<clientId>.** 进行单独配置,其中 clientId 是负载均衡器的名称。默认配置值可以在 spring.cloud.loadbalancer.** 命名空间中设置,并将与客户端特定值合并,客户端特定值优先。

示例 5. application.yml
spring:
  cloud:
    loadbalancer:
      health-check:
        initial-delay: 1s
      clients:
        myclient:
          health-check:
            interval: 30s

上面的示例将产生一个合并的健康检查 @ConfigurationProperties 对象,其中 initial-delay=1sinterval=30s

每个客户端配置属性适用于大多数属性,除了以下全局属性

  • spring.cloud.loadbalancer.enabled - 全局启用或禁用负载均衡

  • spring.cloud.loadbalancer.retry.enabled - 全局启用或禁用负载均衡重试。如果你全局启用了它,仍然可以使用带 client 前缀的属性为特定客户端禁用重试,但不能反过来。

  • spring.cloud.loadbalancer.cache.enabled - 全局启用或禁用 LoadBalancer 缓存。如果你全局启用了它,仍然可以通过创建一个自定义配置,该配置的 ServiceInstanceListSupplier 委托层级中不包含 CachingServiceInstanceListSupplier,来为特定客户端禁用缓存,但不能反过来。

  • spring.cloud.loadbalancer.stats.micrometer.enabled - 全局启用或禁用 LoadBalancer Micrometer 指标

对于已经使用映射的属性,你无需使用 clients 关键字即可为每个客户端指定不同的值(例如,hintshealth-check.path),我们保留了这种行为以保持库的向后兼容性。它将在下一个主要版本中修改。
4.0.4 开始,我们在 LoadBalancerProperties 中引入了 callGetWithRequestOnDelegates 标志。如果此标志设置为 true,则对于从 DelegatingServiceInstanceListSupplier 派生且尚未实现 ServiceInstanceListSupplier#get(Request request) 方法的类,该方法将调用 delegate.get(request)。但 CachingServiceInstanceListSupplierHealthCheckServiceInstanceListSupplier 除外,它们应直接放置在执行网络实例检索的供应商(在进行任何基于请求的过滤之前)之后。对于 4.0.x 版本,该标志默认为 false,但从 4.1.0 版本开始,它将默认为 true

3.18. AOT 和 Native Image 支持

4.0.0 开始,Spring Cloud LoadBalancer 支持 Spring AOT 转换和 Native Image。但是,要使用此功能,你需要显式定义 LoadBalancerClient 的服务 ID。你可以通过使用 @LoadBalancerClient 注解的 valuename 属性,或者作为 spring.cloud.loadbalancer.eager-load.clients 属性的值来做到这一点。

4. Spring Cloud Circuit Breaker

4.1. 简介

Spring Cloud Circuit Breaker 为不同的断路器实现提供了一个抽象层。它提供了一个一致的 API 供你在应用程序中使用,让你(开发者)可以根据应用程序的需求选择最适合的断路器实现。

4.1.1. 支持的实现

Spring Cloud 支持以下断路器实现

4.2. 核心概念

要在代码中创建断路器,你可以使用 CircuitBreakerFactory API。当你在类路径中包含 Spring Cloud Circuit Breaker starter 时,会自动为你创建一个实现此 API 的 bean。以下示例展示了如何使用此 API 的简单示例

@Service
public static class DemoControllerService {
    private RestTemplate rest;
    private CircuitBreakerFactory cbFactory;

    public DemoControllerService(RestTemplate rest, CircuitBreakerFactory cbFactory) {
        this.rest = rest;
        this.cbFactory = cbFactory;
    }

    public String slow() {
        return cbFactory.create("slow").run(() -> rest.getForObject("/slow", String.class), throwable -> "fallback");
    }

}

CircuitBreakerFactory.create API 创建一个名为 CircuitBreaker 的类实例。run 方法接受一个 Supplier 和一个 FunctionSupplier 是你要包装在断路器中的代码。Function 是在断路器跳闸时运行的备用逻辑 (fallback)。此函数会收到导致备用逻辑触发的 Throwable。如果不想提供备用逻辑,可以选择排除它。

4.2.1. 响应式代码中的断路器

如果类路径中有 Project Reactor,你也可以在响应式代码中使用 ReactiveCircuitBreakerFactory。以下示例展示了如何做到这一点

@Service
public static class DemoControllerService {
    private ReactiveCircuitBreakerFactory cbFactory;
    private WebClient webClient;


    public DemoControllerService(WebClient webClient, ReactiveCircuitBreakerFactory cbFactory) {
        this.webClient = webClient;
        this.cbFactory = cbFactory;
    }

    public Mono<String> slow() {
        return webClient.get().uri("/slow").retrieve().bodyToMono(String.class).transform(
        it -> cbFactory.create("slow").run(it, throwable -> return Mono.just("fallback")));
    }
}

ReactiveCircuitBreakerFactory.create API 创建一个名为 ReactiveCircuitBreaker 的类实例。run 方法接受一个 MonoFlux 并将其包装在断路器中。你可以选择性地提供一个备用 Function,该函数在断路器跳闸时调用,并接收导致失败的 Throwable

4.3. 配置

你可以通过创建类型为 Customizer 的 bean 来配置断路器。Customizer 接口有一个方法(名为 customize),该方法接受要自定义的 Object

有关如何自定义给定实现的详细信息,请参阅以下文档

一些 CircuitBreaker 实现,例如 Resilience4JCircuitBreaker,会在每次调用 CircuitBreaker#run 时调用 customize 方法。这可能效率低下。在这种情况下,你可以使用 CircuitBreaker#once 方法。在多次调用 customize 没有意义的情况下,例如在消费 Resilience4j 的事件时,这非常有用。

以下示例展示了每个 io.github.resilience4j.circuitbreaker.CircuitBreaker 消费事件的方式。

Customizer.once(circuitBreaker -> {
  circuitBreaker.getEventPublisher()
    .onStateTransition(event -> log.info("{}: {}", event.getCircuitBreakerName(), event.getStateTransition()));
}, CircuitBreaker::getName)

5. CachedRandomPropertySource

Spring Cloud Context 提供了一个基于键缓存随机值的 PropertySource。除了缓存功能外,它的工作方式与 Spring Boot 的 RandomValuePropertySource 相同。这种随机值在即使 Spring Application 上下文重新启动后也希望保持一致的随机值的情况下可能很有用。属性值采用 cachedrandom.[yourkey].[type] 的形式,其中 yourkey 是缓存中的键。type 值可以是 Spring Boot 的 RandomValuePropertySource 支持的任何类型。

myrandom=${cachedrandom.appname.value}

6. Security

6.1. 单点登录

所有 OAuth2 SSO 和资源服务器功能在 1.3 版本中已移至 Spring Boot。你可以在Spring Boot 用户指南中找到文档。

6.1.1. 客户端令牌中继

如果你的应用程序是面向用户的 OAuth2 客户端(即声明了 @EnableOAuth2Sso@EnableOAuth2Client),那么它会从 Spring Boot 中获得一个请求作用域的 OAuth2ClientContext。你可以从此上下文和一个自动注入的 OAuth2ProtectedResourceDetails 创建自己的 OAuth2RestTemplate,然后上下文将始终向下游转发访问令牌,如果访问令牌过期,也会自动刷新。(这些是 Spring Security 和 Spring Boot 的功能。)

6.1.2. 资源服务器令牌中继

如果你的应用程序有 @EnableResourceServer,你可能希望将传入的令牌向下游中继到其他服务。如果你使用 RestTemplate 来调用下游服务,那么这只是如何使用正确的上下文创建模板的问题。

如果你的服务使用 UserInfoTokenServices 来认证传入的令牌(即使用了 security.oauth2.user-info-uri 配置),那么你可以简单地使用自动注入的 OAuth2ClientContext 创建一个 OAuth2RestTemplate(它将在到达后端代码之前由认证过程填充)。等效地(使用 Spring Boot 1.4),你可以在配置中注入 UserInfoRestTemplateFactory 并获取其 OAuth2RestTemplate。例如

MyConfiguration.java
@Bean
public OAuth2RestTemplate restTemplate(UserInfoRestTemplateFactory factory) {
    return factory.getUserInfoRestTemplate();
}

然后这个 rest template 将拥有与认证过滤器使用的相同的 OAuth2ClientContext(请求作用域),因此你可以使用它发送带有相同访问令牌的请求。

如果你的应用程序未使用 UserInfoTokenServices 但仍是客户端(即声明了 @EnableOAuth2Client@EnableOAuth2Sso),那么使用 Spring Security Cloud,用户从自动注入的 @Autowired OAuth2Context 创建的任何 OAuth2RestOperations 也将转发令牌。此功能默认作为 MVC handler interceptor 实现,因此仅在 Spring MVC 中有效。如果你不使用 MVC,可以使用自定义过滤器或 AOP 拦截器包装 AccessTokenContextRelay 来提供相同的功能。

这是一个基本示例,展示了在别处创建的自动注入 rest template 的使用("foo.com" 是一个接受与周围应用程序相同令牌的资源服务器)

MyController.java
@Autowired
private OAuth2RestOperations restTemplate;

@RequestMapping("/relay")
public String relay() {
    ResponseEntity<String> response =
      restTemplate.getForEntity("https://foo.com/bar", String.class);
    return "Success! (" + response.getBody() + ")";
}

如果你不想转发令牌(这是一个有效的选择,因为你可能希望以自己的身份行事,而不是发送令牌给你的客户端),那么你只需创建自己的 OAuth2Context,而不是自动注入默认的。

如果 OAuth2ClientContext 可用,Feign 客户端也会接收使用它的拦截器,因此它们也应该在 RestTemplate 可以进行令牌中继的任何地方进行令牌中继。

7. 配置属性

要查看所有 Spring Cloud Commons 相关的配置属性列表,请查看附录页面