雲原生是一種應用程式開發風格,它鼓勵在持續交付和價值驅動開發領域輕鬆採用最佳實踐。一個相關的領域是構建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: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 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://springframework.tw/projects/spring-boot#learn].
If you want to learn more about the Spring Cloud Release train compatibility, you can visit this page [https://springframework.tw/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 相關的配置屬性列表,請檢視附錄頁面