使用 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 社区所有即将举行的活动。

查看所有