使用 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 屬性來靜態啟用客戶端負載均衡。它是一個對映,其鍵是服務名稱(由 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 伺服器
./gradlew :eureka-server:bootRun

等待直到您看到 Eureka 伺服器已啟動。

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

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

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 儀表板中看到只有一個例項可用,並且會看到一些日誌訊息抱怨埠 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 社群所有即將舉行的活動。

檢視所有