使用 Spring Cloud Gateway 的主動健康檢查策略

工程 | Ignacio Lozano | 2023 年 7 月 5 日 | ...

使用 Spring Cloud Gateway 的主動健康檢查策略

如今,應用程式被構建為小型獨立上游服務的集合。這加快了開發速度,並允許模組專注於特定職責,從而提高其質量。這是使用微服務方法的主要優勢之一。然而,從一個服務跳轉到另一個服務可能會增加額外的延遲,而當服務沒有響應時,這種延遲可能會急劇增加。

如果您執行微服務,您會希望防止您的上游服務在無法正常工作時被呼叫。即使使用斷路器模式也可能在響應時間上產生額外的開銷。因此,有時最好主動檢查您的上游服務,以驗證它們在需要之前是否已準備就緒。

健康檢查是一種根據服務狀態確定其是否能正確響應的方法,可以防止超時和錯誤。

被動健康檢查是在請求處理過程中進行的。如果服務最終不健康,應用程式將返回失敗,並將該端點標記為不健康。這會增加額外的延遲。

主動健康檢查會在收到請求之前在後臺檢查並移除不健康的服務。它不會增加額外的延遲。

最後但同樣重要的是,這些特性可以與斷路器庫結合使用,以便在不遭受第一次失敗懲罰的情況下立即回退到備用端點。

目標是透過負載均衡策略將請求轉發到健康的的上游服務。

Active Health Check Diagram

本文分為兩部分

  1. “您需要的 Spring 特性” - 描述實現主動健康檢查所需的 Spring 特性。
  2. “為您的服務註冊端點” - 探討為路由新增一個或多個端點的一些方法。

1. 您需要的 Spring 特性

Spring 中有一些特性可以幫助您實現主動健康檢查。

  • Spring Cloud Load Balancer (SLB) 是一個客戶端負載均衡器,允許在不同的上游服務端點之間平衡流量。它是 Spring Cloud 專案的一部分,包含在 spring-cloud-commons 庫中(請參閱 SLB 文件)。
  • 客戶端服務發現特性允許客戶端查詢並與服務通訊,而無需硬編碼主機名和埠。它也包含在 spring-cloud-commons 庫中(請參閱服務發現文件)。

Spring Cloud Gateway Spring Cloud Gateway 提供了一個基於 Spring 和 Java 構建 API 閘道器的庫。它透過 LoadBalancerClientFilter/ReactiveLoadBalancerClientFilter 全域性過濾器支援上述特性。在本文中,您可以看到使用這些全域性過濾器的不同方法。

首先,讓我們探討其中的一些特性。

Spring Cloud Load Balancer 過濾器

Spring Cloud 中包含一個用於負載均衡的全域性過濾器,可以透過使用特殊的 URI 符號啟用:lb://your-service-name

spring:
 cloud:
   gateway:
     routes:
       - id: myRoute
         uri: lb://your-service-name
         predicates:
         - Path=/service/**

負載均衡器過濾器 ReactiveLoadBalancerClientFilter(用於響應式應用程式)將檢測 URI 並將其替換為與 "your-service-name" 關聯的可用端點。

請注意,您需要在服務發現註冊中心註冊 "your-service-name"。我們將在後面的章節中看到不同的實現方法。

主動健康檢查

預設情況下,即使上游服務不健康,流量也會被路由到它們。為了避免選擇不健康的服務,您可以啟用 Spring Cloud 負載均衡器客戶端提供的 health-check 配置。

    spring:
      cloud:  
        loadbalancer:  
          configurations: health-check

所有端點將透過自動使用 Spring Boot Actuator 健康端點進行定期檢查。您還可以自定義一些選項,例如 spring.cloud.loadbalancer.health-check.<your-service-name>.pathspring.cloud.loadbalancer.health-check.interval

預設的健康檢查配置使用 /actuator/health 端點檢查上游服務端點,這需要在您的上游服務中啟用 Spring Actuator。

有關更多選項,請查閱 LoadBalancerClientsPropertiesLoadBalancerProperties 類。

Spring Cloud Gateway 中有一個內建功能,它將把所有可用服務部署為路由。本文描述的是相反的情況,我們宣告的是包含主動健康檢查的負載均衡路由。

2. 為您的服務註冊端點

在上一節中,您指定了一個負載均衡的 URI (lb://your-service-name),但現在您需要註冊與該 URI 的服務名稱關聯的端點。我們將在以下章節中探討一些方法。

靜態方法

您可以透過配置 spring.cloud.discovery.client.simple.instances 屬性來靜態啟用客戶端負載均衡。它是一個 map,其中鍵是服務名稱(由 lb:// URI 使用),值是指向上游服務的 org.springframework.cloud.client.ServiceInstance 物件陣列。

靜態負載均衡的一些好處包括

  • 負載均衡可以將流量分配到多個例項之間,分擔服務的壓力並降低崩潰的可能性。
  • 容錯。

問題在於您在配置中靜態設定了上游服務。如果您需要更改列表,則需要重新啟動應用程式。

示例

spring:
  cloud:
    gateway:
      routes:
        - uri: lb://hello-service # Load Balancer URI handled by ReactiveLoadBalancerClientFilter
          predicates:
            - Path=/hello
    loadbalancer:
      configurations: health-check # Required for enabling SDC with health checks
    discovery:
      client:
        simple: # SimpleDiscoveryClient to configure statically services
          instances:
            hello-service:
              - secure: false
                port: 8090
                host: localhost
                serviceId: hello-service
                instanceId: hello-service-1
              - secure: false
                port: 8091
                host: localhost
                serviceId: hello-service
                instanceId: hello-service-2

嘗試

  1. 執行伺服器
# Run server 1
SERVER_PORT=8090 ./gradlew :service:bootRun
# Run server 2
SERVER_PORT=8091 ./gradlew :service:bootRun
  1. 檢查 https://:8090/actuator/health 是否為 "UP"
curl https://:8090/actuator/health
 {"status":"UP"}
  1. 測試 https://:8080/hello 響應 200 OK
curl localhost:8090/hello
{ "message": "hello world!"}%
  1. 執行 Spring Cloud Gateway
./gradlew :1-service-disc-by-properties:bootRun
  1. 測試 Spring Cloud Gateway 均衡器
curl localhost:8881/hello
{ "message": "hello world from port 8090!"}%
curl localhost:8881/hello
{ "message": "hello world from port 8091!"}%

您可能需要多次執行前面的命令才能從不同的伺服器獲得響應。

  1. 透過向 https://:8090/status/false 傳送 PUT 請求將伺服器 1 標記為不健康
curl localhost:8090/status/false -X PUT
  1. 檢查 https://:8090/actuator/status 是否為 "DOWN"
curl https://:8090/actuator/health
{"status":"DOWN"}
  1. 多次向 https://:8881/hello 傳送 GET 請求,您將看到只收到來自埠 8091 的響應

您可能會收到來自埠 8090 的響應,這是因為在您傳送請求時健康檢查尚未檢查該端點。間隔可以在 spring.cloud.loadbalancer.health-check.interval 屬性中修改。

此外,您可能會看到一些訊息描述其中一個上游端點不健康,因此不可用。

2023-05-08 14:59:53.151 DEBUG 9906 --- [ctor-http-nio-3] r.n.http.client.HttpClientOperations     : [12d42e83-77, L:/127.0.0.1:57439 - R:localhost/127.0.0.1:8090] Received response (auto-read:false) : RESPONSE(decodeResult: success, version: HTTP/1.1)
HTTP/1.1 503 Service Unavailable
curl localhost:8881/hello
{ "message": "hello world from port 8091!"}%
  1. 透過向 https://:8091/status/false 傳送 PUT 請求將伺服器 2 標記為不健康
curl localhost:8091/status/false -X PUT
  1. 多次向 https://:8881/hello 傳送 GET 請求,您將看到它響應 "503 Service Unavailable"
curl localhost:8881/hello
{"timestamp":"2023-05-08T13:07:48.704+00:00","path":"/hello","status":503,"error":"Service Unavailable","requestId":"6b5d6010-199"}%
  1. 停止前面步驟中啟動的所有伺服器

Eureka 整合(+複雜,動態)

靜態配置不夠靈活,但使用 Eureka 作為服務發現可以彌補這一缺點。

代價是您的架構中需要一個新元件,這可能會增加您的維護負擔。對於某些客戶端來說,這可能不是一個可行的選擇。

以下示例配置了 Eureka 整合

    spring:
      application:
        name: scg-client-with-eureka
      cloud:
        loadbalancer:
          configurations: health-check # Note: required for enabling SDC with health checks - remove this line if you want to reproduce issues because not using health checks in LB
          # Note: LoadBalancerCacheProperties.ttl (or spring.cloud.loadbalancer.cache.ttl) is 35 by default - You will need to wait 35secs after an instance turns healthy
        gateway:
          httpclient:
            wiretap: true
          routes:
            - uri: lb://hello-service
              predicates:
                - Path=/headers
              filters:
                - StripPrefix=0

    eureka:
      client:
        webclient:
          enabled: true
        serviceUrl:
          defaultZone: https://:8761/eureka
        fetchRegistry: true
        registerWithEureka: false
      instance:
        preferIpAddress: true

嘗試

  1. 執行 Eureka Server
./gradlew :eureka-server:bootRun

等待直到您看到 Eureka Server 已啟動

2023-06-26 12:51:46.901  INFO 88601 --- [       Thread-9] e.s.EurekaServerInitializerConfiguration : Started Eureka Server
  1. 執行包含 eureka 配置檔案的伺服器
# Run server 1
SPRING_PROFILES_ACTIVE=eureka SERVER_PORT=8090 ./gradlew :service:bootRun
# Run server 2
SPRING_PROFILES_ACTIVE=eureka SERVER_PORT=8091 ./gradlew :service:bootRun

您應該在步驟 1 的伺服器日誌中看到伺服器例項已新增到 Eureka。

2023-06-26 12:52:50.805  INFO 88601 --- [nio-8761-exec-3] c.n.e.registry.AbstractInstanceRegistry  : Registered instance HELLO-SERVICE/192.168.0.14:hello-service:8090 with status UP (replication=true)
2023-06-26 12:53:29.127  INFO 88601 --- [nio-8761-exec-9] c.n.e.registry.AbstractInstanceRegistry  : Registered instance HELLO-SERVICE/192.168.0.14:hello-service:8091 with status UP (replication=true)
  1. 訪問 https://:8761/ 並檢查伺服器是否已作為 hello-service 應用程式的例項包含在內

  2. 執行 Spring Cloud Gateway

SERVER_PORT=8883 ./gradlew :3-eureka-service-disc:bootRun

5. 測試 Spring Cloud Gateway 均衡器

curl localhost:8883/hello
{ "message": "hello world from port 8090!"}%
curl localhost:8883/hello
{ "message": "hello world from port 8091!"}%
  1. 透過向 https://:8090/status/false 傳送 PUT 請求將伺服器 1 標記為不健康
curl localhost:8090/status/false -X PUT

您應該在 Eureka Dashboard 中看到只有一個可用例項,並且會看到一些日誌訊息抱怨埠 8090 上的服務不可用。健康檢查不是立即的,因此您可能需要等待幾秒鐘才能看到例項被標記為 DOWN。

  1. 停止前面步驟中啟動的所有伺服器

路由級別的自定義過濾器(動態方法)

如您所見,Spring Cloud Gateway 提供了建立自定義過濾器的選項。它還允許您在不重新啟動閘道器的情況下應用過濾器和更改路由。

在本節中,您將看到一個自定義過濾器實現,該實現使用 Spring Cloud Gateway 路由配置來設定服務的負載均衡和健康檢查。

如果您的專案中已經有服務發現伺服器,這可能不是您的最佳選擇。如果還沒有,這是一個簡單且廉價的方式將這兩個重要特性整合到您的專案中。

    spring:
      application:
        name: custom-service-disc
      cloud:
        loadbalancer:
          configurations: health-check # Note: required for enabling SDC with health checks - remove this line if you want to reproduce issues because not using health checks in LB
          # Note: LoadBalancerCacheProperties.ttl (or spring.cloud.loadbalancer.cache.ttl) is 35 by default - You will need to wait 35secs after an instance turns healthy
        gateway:
          routes:
            - uri: lb://hello-service
              id: load-balanced
              predicates:
                - Path=/load-balanced/**
              filters:
                - StripPrefix=1
                - LoadBalancer=localhost:8090;localhost:8091;localhost:8092

新的 LoadBalancer 路由過濾器允許您配置與 lb://hello-service 負載均衡 URI 相關的上游服務端點。

@Component
public class LoadBalancerGatewayFilterFactory extends AbstractGatewayFilterFactory<LoadBalancerGatewayFilterFactory.MyConfiguration> {

	// ...

	@Override
	public GatewayFilter apply(MyConfiguration config) {
		return (exchange, chain) -> {
			final Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
			if (StringUtils.hasText(config.getInstances()) && route.getUri().getScheme().equals("lb")) {
				config.getServiceInstances(route.getUri().getHost()).forEach(discoveryClient::addInstance);
			}

			return chain.filter(exchange);
		};
	}

如果路由匹配 lb://<service-host> 模式,LoadBalancerGatewayFilterFactory 會將來自過濾器配置的所有上游服務端點與 service-host 關聯。

在內部,我們包含了一個新的 ReactiveCustomDiscoveryClient 服務發現客戶端實現,用於在程式碼中管理上游服務端點。Spring 會檢測到這個 bean 並在用於確定可用端點的 DiscoveryClient 列表中優先使用它。

嘗試

  1. 執行伺服器
# Run server 1
SERVER_PORT=8090 ./gradlew :service:bootRun
# Run server 2
SERVER_PORT=8091 ./gradlew :service:bootRun
  1. 檢查 https://:8090/actuator/health 是否為 "UP"
curl https://:8090/actuator/health
{"status":"UP"}
  1. 測試 https://:8080/hello 響應 200 OK
curl localhost:8090/hello
{ "message": "hello world!"}%
  1. 執行 Spring Cloud Gateway
SERVER_PORT=8882 ./gradlew :2-custom-service-disc:bootRun
  1. 測試 Spring Cloud Gateway 均衡器
curl localhost:8882/hello
{ "message": "hello world from port 8090!"}%
curl localhost:8882/hello
{ "message": "hello world from port 8091!"}%

您可能需要多次執行前面的命令才能從不同的伺服器獲得響應。

  1. 透過向 https://:8090/status/false 傳送 PUT 請求將伺服器 1 標記為不健康
curl localhost:8090/status/false -X PUT
  1. 檢查 https://:8090/actuator/status 是否為 "DOWN"
curl https://:8090/actuator/health
{"status":"DOWN"}
  1. 多次向 https://:8881/hello 傳送 GET 請求,您將看到只收到來自埠 8091 的響應

您可能會收到來自埠 8090 的響應,這是因為在您傳送請求時健康檢查尚未檢查該端點。間隔可以在 spring.cloud.loadbalancer.health-check.interval 屬性中修改。

此外,您可能會看到一些訊息描述其中一個上游端點不健康,因此不可用。

2023-05-08 15:59:53.151 DEBUG 9906 --- [ctor-http-nio-2] r.n.http.client.HttpClientOperations     : [12d42e83-77, L:/127.0.0.1:57439 - R:localhost/127.0.0.1:8090] Received response (auto-read:false) : RESPONSE(decodeResult: success, version: HTTP/1.1)
HTTP/1.1 503 Service Unavailable
curl localhost:8882/hello
{ "message": "hello world from port 8091!"}%
  1. 透過向 https://:8091/status/false 傳送 PUT 請求將伺服器 2 標記為不健康
curl localhost:8091/status/false -X PUT
  1. 多次向 https://:8881/hello 傳送 GET 請求,您將看到它響應 "503 Service Unavailable"
curl localhost:8882/hello
{"timestamp":"2023-05-08T14:07:48.704+00:00","path":"/hello","status":503,"error":"Service Unavailable","requestId":"6b5d6010-199"}%
  1. 停止前面步驟中啟動的所有伺服器

下一步

在本文中,您看到了在專案中實現負載均衡和主動健康檢查的多種方法。

  • 從適用於上游服務數量不發生變化的基礎專案或概念驗證的靜態方法。
  • 作為一種更動態的方法,可以使用 Eureka 或 Spring Cloud Gateway 過濾器。

總而言之,您還看到如果您的架構不需要額外新增元件,那麼 Spring Cloud Gateway 方法是一個不錯的選擇。

附加資源

想了解更多關於 Spring Cloud 的資訊嗎?歡迎加入我們的 Spring Academy 虛擬課程!

只需在路由中新增一個屬性即可實現主動健康檢查,而無需親自動手?請檢視我們支援 Kubernetes 的商業平臺

訂閱 Spring 新聞通訊

訂閱 Spring 新聞通訊,保持聯絡

訂閱

快人一步

VMware 提供培訓和認證,助力您快速進步。

瞭解更多

獲取支援

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案,只需一次訂閱。

瞭解更多

即將舉行的活動

檢視 Spring 社群所有即將舉行的活動。

檢視全部