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

其中许多功能由 Spring Cloud 构建于其上的 Spring Boot 涵盖。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)。

如果您由于“Illegal key size”而收到异常,并且您使用的是 Sun 的 JDK,则需要安装 Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files。有关更多信息,请参阅以下链接

将文件解压到您使用的任何 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:https://: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 配置文件处于活动状态,则为相关文件):如果您有 bootstrap.yml(或 .properties),则这些属性用于配置引导上下文。然后,当其父上下文设置时,它们将添加到子上下文。它们的优先级低于 application.yml(或 .properties)以及作为创建 Spring Boot 应用程序的正常过程添加到子上下文的任何其他属性源。有关如何自定义这些属性源内容的信息,请参阅“更改引导属性的位置”。

由于属性源的排序规则,“引导”条目优先。但是,请注意,这些不包含 bootstrap.yml 中的任何数据,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。实际上,它们用于通过在其 Environment 中设置这些属性来设置引导 ApplicationContext。如果存在活动配置文件(来自 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 两次。第一次获取将检索没有任何配置文件的任何属性源。这些属性源将有机会使用 spring.profiles.active 激活配置文件。在主应用程序上下文启动后,PropertySourceLocators 将第二次调用,这次使用任何活动配置文件,允许 PropertySourceLocators 查找带有配置文件的任何其他 PropertySources

1.7. 日志配置

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

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

1.8. 环境更改

应用程序侦听 EnvironmentChangeEvent 并以几种标准方式响应更改(可以按常规方式将其他 ApplicationListeners 添加为 @Beans)。当观察到 EnvironmentChangeEvent 时,它会有一个已更改的键值列表,应用程序使用这些键值来

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

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

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

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

1.9. 刷新范围

当配置发生更改时,标记为 @RefreshScope 的 Spring @Bean 会得到特殊处理。此功能解决了有状态 bean 的问题,这些 bean 的配置仅在其初始化时注入。例如,如果 DataSource 在通过 Environment 更改数据库 URL 时具有打开的连接,您可能希望这些连接的持有者能够完成他们正在做的事情。然后,下次有东西从连接池中借用连接时,它会获得一个带有新 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 转换和本地镜像不支持上下文刷新。对于 AOT 和本地镜像,需要将 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 扩展。

如果您由于“Illegal key size”而收到异常,并且您使用的是 Sun 的 JDK,则需要安装 Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files。有关更多信息,请参阅以下链接

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

1.11. 端点

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

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

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

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

  • /actuator/pause/actuator/resume 调用 Lifecycle 方法(ApplicationContext 上的 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.EnableDiscoveryClientDiscoveryClient 实现的示例包括 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

  • 要禁用描述字段,请设置 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

如果类路径中没有服务注册表支持的 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 端点

Spring Cloud Commons 提供了一个 /serviceregistry Actuator 端点。此端点依赖于 Spring 应用程序上下文中的 Registration bean。使用 GET 调用 /serviceregistry 返回 Registration 的状态。使用 POST 到相同端点并带有 JSON 主体将当前 Registration 的状态更改为新值。JSON 主体必须包含带有首选值的 status 字段。请参阅您使用的 ServiceRegistry 实现的文档,了解更新状态时允许的值以及返回的状态值。例如,Eureka 支持的状态包括 UPDOWNOUT_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,您需要在类路径中拥有负载均衡器实现。将 Spring Cloud LoadBalancer 启动器 添加到您的项目中即可使用它。

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,您需要在类路径中拥有一个负载均衡器实现。我们建议您将 Spring Cloud LoadBalancer 启动器 添加到您的项目中。然后,底层使用 ReactiveLoadBalancer

2.4.1. 重试失败的请求

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

如果您想禁用类路径中的 Spring Retry 或 Reactive Retry 的重试逻辑,您可以设置 spring.cloud.loadbalancer.retry.enabled=false

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

对于响应式实现,您只需通过将 spring.cloud.loadbalancer.retry.backoff.enabled 设置为 false 来启用它。

你可以设置

  • 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 - 设置最大退避持续时间(默认值为毫秒的最大长整型值)- 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,因为这曾经是阻塞实现的默认模式。
可以单独配置各个负载均衡器客户端,其属性与上述相同,但前缀为 spring.cloud.loadbalancer.clients.<clientId>.*,其中 clientId 是负载均衡器的名称。
对于负载均衡重试,默认情况下,我们将 ServiceInstanceListSupplier bean 包装在 RetryAwareServiceInstanceListSupplier 中,以便在可用时选择与之前选择的实例不同的实例。您可以通过将 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 启动器 添加到您的项目中,并且 spring-webflux 在类路径中,则 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 在类路径中,则 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 在类路径中时,才会创建 OkHttpClientFactory bean。此外,Spring Cloud Commons 提供了用于创建两个客户端使用的连接管理器的 bean:Apache HTTP 客户端的 ApacheHttpClientConnectionManagerFactory 和 OK HTTP 客户端的 OkHttpClientConnectionPoolFactory。如果您想自定义下游项目中 HTTP 客户端的创建方式,您可以提供自己的这些 bean 的实现。此外,如果您提供 HttpClientBuilderOkHttpClient.Builder 类型的 bean,则默认工厂将这些构建器用作返回给下游项目的构建器的基础。您还可以通过将 spring.cloud.httpclientfactories.apache.enabledspring.cloud.httpclientfactories.ok.enabled 设置为 false 来禁用这些 bean 的创建。

2.10. 启用功能

Spring Cloud Commons 提供了一个 /features Actuator 端点。此端点返回类路径中可用的功能以及它们是否已启用。返回的信息包括功能类型、名称、版本和供应商。

2.10.1. 功能类型

“功能”有两种类型:抽象功能和命名功能。

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

命名功能是指没有特定类实现的功能。这些功能包括“断路器”、“API 网关”、“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 要求不兼容,它将中断,并附带一份报告,显示具体出了什么问题。

目前我们验证您的类路径中添加了哪个版本的 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 接口,并为其提供了基于轮询随机的实现。为了从响应式 ServiceInstanceListSupplier 中选择实例。目前我们支持基于服务发现的 ServiceInstanceListSupplier 实现,该实现使用类路径中可用的 Discovery Client 从服务发现中检索可用实例。

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

3.1. LoadBalancer 上下文的预加载

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

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

3.2. 负载均衡算法切换

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

例如,可以通过 @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 支持的 LoadBalancer 缓存实现

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

如果您正在使用 Caffeine,您还可以通过在 spring.cloud.loadbalancer.cache.caffeine.spec 属性中传递您自己的 Caffeine 规范 来覆盖 LoadBalancer 的默认 Caffeine 缓存设置。

警告:传递您自己的 Caffeine 规范将覆盖任何其他 LoadBalancerCache 设置,包括 通用 LoadBalancer 缓存配置 字段,例如 ttlcapacity

3.4.2. 默认 LoadBalancer 缓存实现

如果类路径中没有 Caffeine,则将使用 DefaultLoadBalancerCache,它会自动随 spring-cloud-starter-loadbalancer 提供。有关如何配置它的信息,请参阅 LoadBalancerCacheConfiguration 部分。

要使用 Caffeine 而不是默认缓存,请将 com.github.ben-manes.caffeine:caffeine 依赖项添加到类路径。

3.4.3. LoadBalancer 缓存配置

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

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

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

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

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 被配置为设置 LoadBalancer 区域。对于其他发现客户端,请设置 spring.cloud.loadbalancer.zone 属性。更多配置即将推出。
要确定检索到的 ServiceInstance 的区域,我们检查其元数据映射中 "zone" 键下的值。

ZonePreferenceServiceInstanceListSupplier 过滤检索到的实例,并且只返回同一区域内的实例。如果区域为 null 或同一区域内没有实例,则返回所有检索到的实例。

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

我们使用委托来处理 ServiceInstanceListSupplier bean。我们建议使用 DiscoveryClientServiceInstanceListSupplier 委托,将其包装在 CachingServiceInstanceListSupplier 中以利用LoadBalancer 缓存机制,然后将生成的 bean 传递到 ZonePreferenceServiceInstanceListSupplier 的构造函数中。

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

public class CustomLoadBalancerConfiguration {

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

3.7. LoadBalancer 实例健康检查

可以为 LoadBalancer 启用计划健康检查。为此提供了 HealthCheckServiceInstanceListSupplier。它定期验证委托 ServiceInstanceListSupplier 提供的实例是否仍然存活,并且只返回健康的实例,除非没有健康的实例 - 此时它返回所有检索到的实例。

这种机制在使用 SimpleDiscoveryClient 时特别有用。对于由实际服务注册表支持的客户端,无需使用,因为我们已经通过查询外部 ServiceDiscovery 获得了健康的实例。
对于每个服务实例数量较少的设置,也建议使用此供应商,以避免在故障实例上重试调用。
如果使用任何服务发现支持的供应商,通常不需要添加此健康检查机制,因为我们直接从服务注册表检索实例的健康状态。
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 添加到您的协作方的依赖项中,除非您计划自行添加此类端点。
默认情况下,healthCheckFlux 将在每个已检索到的活跃 ServiceInstance 上发出。您可以通过将 spring.cloud.loadbalancer.health-check.update-results-list 的值设置为 false 来修改此行为。如果此属性设置为 false,则整个活跃实例序列将首先收集到列表中,然后才发出,这确保 flux 不会在属性中设置的健康检查间隔之间发出值。

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

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

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

public class CustomLoadBalancerConfiguration {

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

3.8. LoadBalancer 的相同实例偏好

您可以这样设置 LoadBalancer,使其优先选择之前选择的实例(如果该实例可用)。

为此,您需要使用 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. LoadBalancer 的基于请求的粘性会话

您可以设置 LoadBalancer,使其优先选择请求 Cookie 中提供的 instanceId 的实例。我们目前支持此功能,前提是请求通过 ClientRequestContextServerHttpRequestContext 传递给 LoadBalancer,这些上下文由 SC LoadBalancer 交换过滤器函数和过滤器使用。

为此,您需要使用 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 对象中传递给 LoadBalancer,并且稍后可以在能够处理它们的 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> 对象作为参数。另一方面,CompletionContext 对象提供给 onComplete(CompletionContext<RES, T, RC> completionContext) 方法。它包含 LoadBalancer Response,包括选定的服务实例、针对该服务实例执行的请求的 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 中注册以下计量器

  • loadbalancer.requests.active:一个仪表,允许您监控任何服务实例当前活动的请求数(服务实例数据可通过标签获取);

  • loadbalancer.requests.success:一个计时器,测量任何已成功向底层客户端传递响应的负载均衡请求的执行时间;

  • loadbalancer.requests.failed:一个计时器,测量任何已因异常而结束的负载均衡请求的执行时间;

  • loadbalancer.requests.discard:一个计数器,测量被丢弃的负载均衡请求数,即 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 缓存。如果您全局启用它,您仍然可以通过创建不包含 CachingServiceInstanceListSupplierServiceInstanceListSupplier 委托层次结构中的自定义配置来为特定客户端禁用缓存,但不能反过来。

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

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

3.18. AOT 和原生镜像支持

4.0.0 开始,Spring Cloud LoadBalancer 支持 Spring AOT 转换和原生镜像。但是,要使用此功能,您需要明确定义您的 LoadBalancerClient 服务 ID。您可以通过使用 @LoadBalancerClient 注解的 valuename 属性,或作为 spring.cloud.loadbalancer.eager-load.clients 属性的值来实现。

4. Spring Cloud 断路器

4.1. 简介

Spring Cloud 断路器提供了跨不同断路器实现的抽象。它提供了在您的应用程序中使用的统一 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 是断路器跳闸时运行的备用方法。该函数会传递导致备用方法触发的 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 应用程序上下文重启后仍然一致的随机值时很有用。属性值的形式为 cachedrandom.[yourkey].[type],其中 yourkey 是缓存中的键。type 值可以是 Spring Boot 的 RandomValuePropertySource 支持的任何类型。

myrandom=${cachedrandom.appname.value}

6. 安全

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 模板将拥有与身份验证过滤器使用的相同的 OAuth2ClientContext(请求范围),因此您可以使用它来发送带有相同访问令牌的请求。

如果您的应用程序没有使用 UserInfoTokenServices,但仍然是一个客户端(即它声明了 @EnableOAuth2Client@EnableOAuth2S2o),那么借助 Spring Security Cloud,用户从 @Autowired OAuth2Context 创建的任何 OAuth2RestOperations 也会转发令牌。此功能默认作为 MVC 处理程序拦截器实现,因此它仅适用于 Spring MVC。如果您不使用 MVC,可以使用自定义过滤器或 AOP 拦截器包装 AccessTokenContextRelay 来提供相同的功能。

这是一个基本示例,展示了使用在其他地方创建的自动装配的 rest 模板("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,而不是自动装配默认的上下文。

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

7. 配置属性

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

© . This site is unofficial and not affiliated with VMware.