雲原生是一種應用程式開發風格,鼓勵輕鬆採用持續交付和價值驅動開發領域的最佳實務。一個相關的學科是建構 十二要素應用程式,其中開發實務與交付和營運目標一致 — 例如,透過使用宣告式程式設計以及管理與監控。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)。

如果您因為「Illegal key size」而收到例外,並且您使用 Sun 的 JDK,則您需要安裝 Java 密碼術擴展 (JCE) 無限制強度管轄策略檔案。請參閱以下連結以取得更多資訊

將檔案解壓縮到您使用的任何 JRE/JDK x64/x86 版本的 JDK/jre/lib/security 資料夾中。

Spring Cloud 是在非限制性的 Apache 2.0 授權下發布的。如果您想為文件的這個章節貢獻,或者如果您發現錯誤,您可以在 {docslink}[github] 找到專案的原始碼和問題追蹤器。

1. Spring Cloud Context:應用程式內容服務

Spring Boot 對於如何使用 Spring 建構應用程式有其主觀觀點。例如,它針對常見的配置檔案具有慣例位置,並且針對常見的管理與監控任務具有端點。Spring Cloud 建構於此之上,並新增了一些系統中許多組件會使用或偶爾需要的功能。

1.1. 啟動應用程式內容

Spring Cloud 應用程式透過建立「啟動」內容來運作,該內容是主要應用程式的父內容。此內容負責從外部來源載入配置屬性,以及解密本機外部配置檔案中的屬性。這兩個內容共用一個 Environment,它是任何 Spring 應用程式的外部屬性來源。依預設,啟動屬性(不是 bootstrap.properties,而是啟動階段期間載入的屬性)以高優先順序新增,因此它們無法被本機配置覆寫。

啟動內容使用與主要應用程式內容不同的慣例來尋找外部配置。您可以改為使用 bootstrap.yml,而不是 application.yml(或 .properties),使啟動和主要內容的外部配置保持良好分離。以下清單顯示一個範例

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

如果您的應用程式需要來自伺服器的任何應用程式特定配置,最好設定 spring.application.name(在 bootstrap.ymlapplication.yml 中)。為了使屬性 spring.application.name 用作應用程式的內容 ID,您必須在 bootstrap.[properties | yml] 中設定它。

如果您想要擷取特定的設定檔配置,您也應該在 bootstrap.[properties | yml] 中設定 spring.profiles.active

您可以透過設定 spring.cloud.bootstrap.enabled=false(例如,在系統屬性中)完全停用啟動程序。

1.2. 應用程式內容階層

如果您從 SpringApplicationSpringApplicationBuilder 建構應用程式內容,則啟動內容會新增為該內容的父內容。Spring 的一項功能是子內容從其父內容繼承屬性來源和設定檔,因此與在沒有 Spring Cloud Config 的情況下建構相同內容相比,「主要」應用程式內容包含其他屬性來源。其他屬性來源為

  • 「bootstrap」:如果在啟動內容中找到任何 PropertySourceLocators,並且它們具有非空屬性,則會出現具有高優先順序的可選 CompositePropertySource。Spring Cloud Config 伺服器的屬性就是一個範例。請參閱「自訂啟動屬性來源」,以瞭解如何自訂此屬性來源的內容。

在 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」項目優先。但是,請注意,這些項目不包含來自 bootstrap.yml 的任何資料,後者的優先順序非常低,但可用於設定預設值。

您可以透過設定您建立的任何 ApplicationContext 的父內容來擴展內容階層 — 例如,透過使用其自己的介面或使用 SpringApplicationBuilder 便利方法(parent()child()sibling())。啟動內容是您自己建立的最資深祖先的父內容。階層中的每個內容都有自己的「bootstrap」(可能為空)屬性來源,以避免無意中將值從父內容向下提升到其後代。如果存在配置伺服器,則階層中的每個內容原則上也可以具有不同的 spring.application.name,因此也具有不同的遠端屬性來源。正常的 Spring 應用程式內容行為規則適用於屬性解析:來自子內容的屬性會依名稱以及屬性來源名稱覆寫父內容中的屬性。(如果子內容具有與父內容同名的屬性來源,則父內容中的值不會包含在子內容中)。

請注意,SpringApplicationBuilder 可讓您在整個階層中共用 Environment,但這不是預設設定。因此,同級內容(特別是)不需要具有相同的設定檔或屬性來源,即使它們可能與其父內容共用通用值。

1.3. 變更啟動屬性的位置

可以透過設定 spring.cloud.bootstrap.name(預設值:bootstrap)、spring.cloud.bootstrap.location(預設值:空)或 spring.cloud.bootstrap.additional-location(預設值:空) — 例如,在系統屬性中,來指定 bootstrap.yml(或 .properties)的位置。

這些屬性的行為與具有相同名稱的 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 伺服器)。依預設,它們無法在本機覆寫。如果您想要讓您的應用程式使用其自己的系統屬性或配置檔案覆寫遠端屬性,則遠端屬性來源必須透過設定 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 新增到主要 SpringApplication

1.6. 自訂啟動屬性來源

啟動程序新增的外部配置的預設屬性來源是 Spring Cloud Config 伺服器,但您可以透過將 PropertySourceLocator 類型的 bean 新增到啟動內容(透過 spring.factories)來新增其他來源。例如,您可以從不同的伺服器或資料庫插入其他屬性。

作為範例,請考慮以下自訂定位器

@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 伺服器屬性來源定位器中所做的那樣)。

如果您建立一個 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 中。具體來說,除非它本身在 @RefreshScope 中,否則任何依賴於這些 bean 的事物都無法依賴它們在啟動重新整理時更新。在這種情況下,它會在重新整理時重建,並且其依賴項會重新注入。屆時,它們會從重新整理的 @Configuration 重新初始化)。
移除配置值,然後執行重新整理將不會更新配置值的存在。配置屬性必須存在,才能在重新整理後更新值。如果您依賴於應用程式中值的存在,您可能想要切換您的邏輯以依賴於它的不存在。另一種選擇是依賴於值變更,而不是不存在於應用程式的配置中。
Spring AOT 轉換和原生映像檔不支援內容重新整理。對於 AOT 和原生映像檔,需要將 spring.cloud.refresh.enabled 設定為 false

1.10. 加密與解密

Spring Cloud 具有 Environment 預處理器,用於在本機解密屬性值。它遵循與 Spring Cloud Config 伺服器相同的規則,並透過 encrypt.* 具有相同的外部配置。因此,您可以使用 {cipher}* 形式的加密值,並且只要存在有效的金鑰,它們就會在主要應用程式內容取得 Environment 設定之前解密。若要在應用程式中使用加密功能,您需要在您的類別路徑中包含 Spring Security RSA(Maven 座標:org.springframework.security:spring-security-rsa),並且您還需要在您的 JVM 中使用完整強度 JCE 擴展。

如果您因為「Illegal key size」而收到例外,並且您使用 Sun 的 JDK,則您需要安裝 Java 密碼術擴展 (JCE) 無限制強度管轄策略檔案。請參閱以下連結以取得更多資訊

將檔案解壓縮到您使用的任何 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 介面的實作。探索用戶端的實作會將配置類別新增到 org.springframework.cloud.client.discovery.EnableDiscoveryClient 金鑰下的 spring.factoriesDiscoveryClient 實作的範例包括 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(s) 來監聽這些事件並做出反應。

如果 spring.cloud.service-registry.auto-registration.enabled 屬性設定為 false,則不會觸發這些事件。

2.2.2. Service Registry 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 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,您的類別路徑中需要有負載平衡器實作。我們建議您將 Spring Cloud LoadBalancer Starter 新增至您的專案。然後,底層會使用 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,因為這曾經是區塊實作的預設模式。
可以個別設定個別的 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. Spring WebFlux WebClientReactorLoadBalancerExchangeFilterFunction

您可以設定 WebClient 以使用 ReactiveLoadBalancer。如果您將 Spring Cloud LoadBalancer Starter 新增至您的專案,且 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://spring.dev.org.tw/projects/spring-boot#learn].
If you want to learn more about the Spring Cloud Release train compatibility, you can visit this page [https://spring.dev.org.tw/projects/spring-cloud#overview] and check the [Release Trains] section.

為了停用此功能,請將 spring.cloud.compatibility-verifier.enabled 設定為 false。如果您想要覆寫相容的 Spring Boot 版本,只需使用逗號分隔的相容 Spring Boot 版本清單設定 spring.cloud.compatibility-verifier.compatible-boot-versions 屬性即可。

3. Spring Cloud LoadBalancer

Spring Cloud 提供了自己的用戶端負載平衡器抽象和實作。對於負載平衡機制,已新增 ReactiveLoadBalancer 介面,並為其提供了循環配置資源 (Round-Robin)隨機實作。為了取得要選取的執行個體,使用了反應式 ServiceInstanceListSupplier。目前,我們支援 ServiceInstanceListSupplier 的服務探索型實作,它使用類別路徑中可用的 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。若要切換到不同的實作,無論是針對選定的服務還是所有服務,您都可以使用自訂 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 Specification 來覆寫 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 快取組態

您可以設定自己的 ttl 值(寫入後經過的時間,超過此時間後應使項目過期),表示為 Duration,方法是傳遞符合 Spring Boot StringDuration 轉換器語法String 作為 spring.cloud.loadbalancer.cache.ttl 屬性的值。您也可以透過設定 spring.cloud.loadbalancer.cache.capacity 屬性的值來設定自己的 LoadBalancer 快取初始容量。

預設設定包括 ttl 設定為 35 秒,預設 initialCapacity256

您也可以將 spring.cloud.loadbalancer.cache.enabled 的值設定為 false,完全停用 LoadBalancer 快取。

雖然基本的非快取實作對於原型設計和測試很有用,但它的效率遠低於快取版本,因此我們建議始終在生產中使用快取版本。如果快取已由 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 會尋找在其 metadataMap 中的 hint 鍵下設定了相符值的服務執行個體。如果找不到相符的執行個體,則會傳回委派提供的所有執行個體。

您可以使用下列範例組態來設定它

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 快取Evictor

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 組態,我們已將 builder() 方法新增至 ServiceInstanceListSupplier 類別。
您也可以使用我們預定義的替代組態,取代預設組態。方法是將 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:一個計數器,用於測量已捨棄的負載平衡請求的數量,即負載平衡器未檢索到要在其上執行請求的服務實例的請求。

有關服務實例、請求資料和響應資料的其他資訊,會在可用時透過標籤新增至指標。

對於某些實作,例如 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

上述範例將產生合併的 health-check @ConfigurationProperties 物件,其中 initial-delay=1sinterval=30s

每個用戶端組態屬性適用於大多數屬性,除了以下全域屬性外

  • spring.cloud.loadbalancer.enabled - 全域啟用或停用負載平衡

  • spring.cloud.loadbalancer.retry.enabled - 全域啟用或停用負載平衡重試。如果您全域啟用它,您仍然可以使用用戶端前綴的屬性停用特定用戶端的重試,但反之則不然

  • spring.cloud.loadbalancer.cache.enabled - 全域啟用或停用 LoadBalancer 快取。如果您全域啟用它,您仍然可以透過建立不包含 CachingServiceInstanceListSupplierServiceInstanceListSupplier 委派階層中的自訂組態來停用特定用戶端的快取,但反之則不然。

  • spring.cloud.loadbalancer.stats.micrometer.enabled - 全域啟用或停用 LoadBalancer Micrometer 指標

對於已經使用對應的屬性,您可以在不使用 clients 關鍵字的情況下,為每個用戶端指定不同的值(例如,hintshealth-check.path),我們保留了該行為,以保持程式庫的向後相容性。它將在下一個主要版本中修改。
4.0.4 開始,我們在 LoadBalancerProperties 中引入了 callGetWithRequestOnDelegates 旗標。如果此旗標設定為 trueServiceInstanceListSupplier#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 方法接受 SupplierFunctionSupplier 是您要包裝在斷路器中的程式碼。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 template 將具有與驗證篩選器使用的相同的 OAuth2ClientContext(請求範圍),因此您可以使用它來傳送具有相同存取權杖的請求。

如果您的應用程式未使用 UserInfoTokenServices,但仍然是用戶端(即它宣告 @EnableOAuth2Client@EnableOAuth2Sso),那麼使用 Spring Security Cloud,使用者從 @Autowired OAuth2Context 建立的任何 OAuth2RestOperations 也將轉發權杖。此功能預設以 MVC 處理常式攔截器實作,因此僅適用於 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,而不是自動裝配預設的權杖。

Feign 用戶端也將擷取使用 OAuth2ClientContext 的攔截器(如果可用),因此它們也應該在 RestTemplate 會轉發權杖的任何地方執行權杖轉發。

7. 組態屬性

若要查看所有 Spring Cloud Commons 相關組態屬性的清單,請查看附錄頁面