使用 Spring Cloud Gateway 实现主动健康检查策略

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

使用 Spring Cloud Gateway 实现主动健康检查策略

如今,应用程序被构建为一系列小型独立的上游服务。这加速了开发,并允许模块专注于特定的职责,从而提高其质量。这是使用微服务方法的主要优势之一。但是,从一个服务跳转到另一个服务可能会增加额外的延迟,当服务没有响应时,这种延迟可能会大幅增加。

如果您运行微服务,您希望防止在服务无法正常工作时调用您的上游服务。即使使用断路器模式也可能导致响应时间延迟。因此,有时最好主动检查您的上游服务以验证它们是否已准备就绪,然后再需要它们。

健康检查是一种确定服务是否可以根据其状态正确响应的方法,从而防止超时和错误。

**被动健康检查**在请求处理期间进行。如果服务最终不健康,应用程序将返回一个错误,标记端点不健康。这可能会增加额外的延迟。

**主动健康检查**将在接收请求之前,在后台检查并丢弃不健康的服务器。它不会增加额外的延迟。

最后但并非最不重要的是,这些功能可以与断路器库结合使用,以便立即回退到备用端点,而不会遭受第一次失败的延迟。

目标是通过使用负载均衡策略,使路由将请求转发到健康的上游服务。

Active Health Check Diagram

本文分为两个部分

  1. “您需要的 Spring 功能” - 描述实现主动健康检查所需的 Spring 功能。
  2. “为您的服务注册端点” - 介绍为您的路由添加一个或多个端点的一些方法。

1. 您需要的 Spring 功能

Spring 中有一些功能可以帮助您实现主动健康检查。

  • **Spring Cloud 负载均衡器** (SLB) 是一种客户端负载均衡器,允许在不同的上游服务端点之间平衡流量。它是 Spring Cloud 项目 的一部分,包含在 spring-cloud-commons 库中(参见 SLB 文档)。
  • 客户端服务发现功能允许客户端查找并与服务通信,而无需硬编码主机名和端口。它也包含在 spring-cloud-commons 库中(参见 服务发现文档)。

**Spring Cloud Gateway** Spring Cloud Gateway 提供了一个库,用于在 Spring 和 Java 之上构建 API 网关。它通过 LoadBalancerClientFilter/ReactiveLoadBalancerClientFilter 全局过滤器支持上述功能。在这篇文章中,您可以看到使用这些全局过滤器中的一个的不同方法。

首先,让我们探索其中的一些功能。

Spring Cloud 负载均衡器过滤器

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://127.0.0.1:8090/actuator/health 是否为“UP”
curl https://127.0.0.1:8090/actuator/health
 {"status":"UP"}
  1. 测试 https://127.0.0.1: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. 将服务器 1 标记为不健康,发送 PUT 请求到 https://127.0.0.1:8090/status/false
curl localhost:8090/status/false -X PUT
  1. 检查 https://127.0.0.1:8090/actuator/status 是否为“DOWN”
curl https://127.0.0.1:8090/actuator/health
{"status":"DOWN"}
  1. 多次运行 GET 请求到 https://127.0.0.1:8881/hello 并查看您是否只从端口 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. 将服务器 2 标记为不健康,发送 PUT 请求到 https://127.0.0.1:8091/status/false
curl localhost:8091/status/false -X PUT
  1. 运行一些 GET 请求到 https://127.0.0.1:8881/hello 并查看它是否返回“503 服务不可用”
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://127.0.0.1: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

您应该在步骤 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://127.0.0.1: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. 将服务器 1 标记为不健康,发送 PUT 请求到 https://127.0.0.1:8090/status/false
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://127.0.0.1:8090/actuator/health 是否为“UP”
curl https://127.0.0.1:8090/actuator/health
{"status":"UP"}
  1. 测试 https://127.0.0.1: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. 将服务器 1 标记为不健康,发送 PUT 请求到 https://127.0.0.1:8090/status/false
curl localhost:8090/status/false -X PUT
  1. 检查 https://127.0.0.1:8090/actuator/status 是否为“DOWN”
curl https://127.0.0.1:8090/actuator/health
{"status":"DOWN"}
  1. 多次向 https://127.0.0.1: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. 将服务器 2 标记为不健康,发送 PUT 请求到 https://127.0.0.1:8091/status/false
curl localhost:8091/status/false -X PUT
  1. 运行一些 GET 请求到 https://127.0.0.1:8881/hello 并查看它是否返回“503 服务不可用”
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 社区中所有即将举行的活动。

查看全部