使用 Consul 进行服务发现

服务发现是基于微服务架构的关键原则之一。尝试手动配置每个客户端或某种形式的约定可能非常困难且非常脆弱。Consul 通过 HTTP APIDNS 提供服务发现服务。Spring Cloud Consul 利用 HTTP API 进行服务注册和发现。这并不妨碍非 Spring Cloud 应用程序利用 DNS 接口。Consul Agent 服务器运行在一个集群中,通过 Gossip 协议进行通信,并使用 Raft 一致性协议

如何激活

要激活 Consul 服务发现,请使用 group 为 org.springframework.cloud、artifact id 为 spring-cloud-starter-consul-discovery 的 starter。有关如何使用当前 Spring Cloud Release Train 设置构建系统的详细信息,请参阅 Spring Cloud 项目页面

向 Consul 注册

当客户端向 Consul 注册时,它会提供关于自身的元数据,例如主机和端口、id、名称和标签。默认会创建一个 HTTP 健康检查,Consul 每隔 10 秒访问 /actuator/health 端点。如果健康检查失败,服务实例将被标记为 critical(严重)。

Consul 客户端示例

@SpringBootApplication
@RestController
public class Application {

    @RequestMapping("/")
    public String home() {
        return "Hello world";
    }

    public static void main(String[] args) {
        new SpringApplicationBuilder(Application.class).web(true).run(args);
    }

}

(即完全正常的 Spring Boot 应用程序)。如果 Consul 客户端位于 localhost:8500 之外的其他位置,则需要进行配置来找到客户端。示例

application.yml
spring:
  cloud:
    consul:
      host: localhost
      port: 8500
如果您使用 Spring Cloud Consul Config,并且已设置 spring.cloud.bootstrap.enabled=truespring.config.use-legacy-processing=true,或使用了 spring-cloud-starter-bootstrap,则上述值需要放在 bootstrap.yml 中,而不是 application.yml 中。

默认的服务名称、实例 id 和端口取自 Environment,它们分别是 ${spring.application.name}、Spring Context ID 和 ${server.port}

要禁用 Consul Discovery Client,您可以将 spring.cloud.consul.discovery.enabled 设置为 false。当 spring.cloud.discovery.enabled 设置为 false 时,Consul Discovery Client 也会被禁用。

要禁用服务注册,您可以将 spring.cloud.consul.discovery.register 设置为 false

将管理注册为独立服务

当通过设置 management.server.port 属性将管理服务器端口设置为与应用程序端口不同的值时,管理服务将注册为与应用程序服务分离的独立服务。例如

application.yml
spring:
  application:
    name: myApp
management:
  server:
    port: 4452

上述配置将注册以下 2 个服务

  • 应用服务

ID: myApp
Name: myApp
  • 管理服务

ID: myApp-management
Name: myApp-management

管理服务将从应用服务继承其 instanceIdserviceName。例如

application.yml
spring:
  application:
    name: myApp
management:
  server:
    port: 4452
spring:
  cloud:
    consul:
      discovery:
        instance-id: custom-service-id
        serviceName: myprefix-${spring.application.name}

上述配置将注册以下 2 个服务

  • 应用服务

ID: custom-service-id
Name: myprefix-myApp
  • 管理服务

ID: custom-service-id-management
Name: myprefix-myApp-management

可以通过以下属性进行进一步自定义

/** Port to register the management service under (defaults to management port) */
spring.cloud.consul.discovery.management-port

/** Suffix to use when registering management service (defaults to "management") */
spring.cloud.consul.discovery.management-suffix

/** Tags to use when registering management service (defaults to "management") */
spring.cloud.consul.discovery.management-tags

HTTP 健康检查

Consul 实例的健康检查默认为 "/actuator/health",这是 Spring Boot Actuator 应用程序中健康端点的默认位置。即使是 Actuator 应用程序,如果您使用了非默认的上下文路径或 servlet 路径(例如 server.servletPath=/foo)或管理端点路径(例如 management.server.servlet.context-path=/admin),也需要更改此设置。

Consul 用于检查健康端点的时间间隔也可以配置。"10s" 和 "1m" 分别表示 10 秒和 1 分钟。

此示例说明了上述内容(更多选项请参阅附录页中的 spring.cloud.consul.discovery.health-check-* 属性)。

application.yml
spring:
  cloud:
    consul:
      discovery:
        healthCheckPath: ${management.server.servlet.context-path}/actuator/health
        healthCheckInterval: 15s

通过设置 spring.cloud.consul.discovery.register-health-check=false,您可以完全禁用 HTTP 健康检查。

应用请求头

可以将请求头应用于健康检查请求。例如,如果您尝试注册使用 Vault BackendSpring Cloud Config 服务器

application.yml
spring:
  cloud:
    consul:
      discovery:
        health-check-headers:
          X-Config-Token: 6442e58b-d1ea-182e-cfa5-cf9cddef0722

根据 HTTP 标准,每个请求头可以有多个值,在这种情况下,可以提供一个数组。

application.yml
spring:
  cloud:
    consul:
      discovery:
        health-check-headers:
          X-Config-Token:
            - "6442e58b-d1ea-182e-cfa5-cf9cddef0722"
            - "Some other value"

TTL 健康检查

可以使用 Consul TTL 检查来替代默认配置的 HTTP 检查。主要区别在于应用程序向 Consul Agent 发送心跳信号,而不是 Consul Agent 向应用程序发送请求。

应用程序用于发送 ping 的时间间隔也可以配置。"10s" 和 "1m" 分别表示 10 秒和 1 分钟。默认是 30 秒。

此示例说明了上述内容(更多选项请参阅附录页中的 spring.cloud.consul.discovery.heartbeat.* 属性)。

application.yml
spring:
  cloud:
    consul:
      discovery:
        heartbeat:
          enabled: true
          ttl: 10s

TTL 应用状态

对于 Spring Boot Actuator 应用程序,状态由其可用的健康端点决定。当健康端点不可用时(无论是被禁用还是并非 Spring Boot Actuator 应用程序),它假定应用程序处于良好健康状态。

查询健康端点时,默认使用根 健康组。通过设置以下属性可以使用不同的健康组

application.yml
spring:
  cloud:
    consul:
      discovery:
        heartbeat:
          actuator-health-group: <your-custom-group-goes-here>

通过设置以下属性,您可以完全禁用健康端点的使用

application.yml
spring:
  cloud:
    consul:
      discovery:
        heartbeat:
          use-actuator-health: false
自定义 TTL 应用状态

如果您想配置自己的应用状态机制,只需实现 ApplicationStatusProvider 接口

MyCustomApplicationStatusProvider.java
@Bean
public class MyCustomApplicationStatusProvider implements ApplicationStatusProvider {
	public CheckStatus currentStatus() {
        return yourMethodToDetermineAppStatusGoesHere();
    }
}

并使其对应用上下文可用

@Bean
public CustomApplicationStatusProvider customAppStatusProvider() {
     return new MyCustomApplicationStatusProvider();
}

Actuator 健康指示器

如果服务实例是 Spring Boot Actuator 应用程序,它可以提供以下 Actuator 健康指示器。

DiscoveryClientHealthIndicator

当 Consul 服务发现激活时,会配置并使 DiscoverClientHealthIndicator 可用于 Actuator 健康端点。此处查看配置选项

ConsulHealthIndicator

配置了一个指标来验证 ConsulClient 的健康状况。

默认情况下,它会检索 Consul Leader 节点的状态和所有已注册的服务。在注册服务众多的部署中,每次健康检查都检索所有服务可能会带来较高的成本。要跳过服务检索,仅检查 Leader 节点状态,请设置 spring.cloud.consul.health-indicator.include-services-query=false

要禁用该指标,请设置 management.health.consul.enabled=false

当应用程序在bootstrap 上下文模式下运行时(默认),此指示器会加载到 bootstrap 上下文中,而不会提供给 Actuator 健康端点。

元数据

Consul 支持服务的元数据。Spring Cloud 的 ServiceInstance 有一个 Map<String, String> metadata 字段,它从服务的 meta 字段填充。要填充 meta 字段,请在 spring.cloud.consul.discovery.metadataspring.cloud.consul.discovery.management-metadata 属性上设置值。

application.yml
spring:
  cloud:
    consul:
      discovery:
        metadata:
          myfield: myvalue
          anotherfield: anothervalue

上述配置将使得服务的 meta 字段包含 myfield→myvalueanotherfield→anothervale

生成的元数据

Consul 自动注册将自动生成一些条目。

表 1. 自动生成的元数据

'group'

属性 spring.cloud.consul.discovery.instance-group。仅当 instance-group 不为空时才生成此值。

'secure'

如果属性 spring.cloud.consul.discovery.scheme 等于 'https',则为 true,否则为 false。

属性 spring.cloud.consul.discovery.default-zone-metadata-name,默认为 'zone'

属性 spring.cloud.consul.discovery.instance-zone。仅当 instance-zone 不为空时才生成此值。

较旧版本的 Spring Cloud Consul 通过解析 spring.cloud.consul.discovery.tags 属性来填充 Spring Cloud Commons 中的 ServiceInstance.getMetadata() 方法。此功能不再支持,请迁移到使用 spring.cloud.consul.discovery.metadata map。

使 Consul 实例 ID 唯一

默认情况下,Consul 实例注册时使用的 ID 等于其 Spring 应用上下文 ID。默认情况下,Spring 应用上下文 ID 是 ${spring.application.name}:comma,separated,profiles:${server.port}。大多数情况下,这将允许一个服务的多个实例运行在同一台机器上。如果需要更高的唯一性,使用 Spring Cloud 可以通过在 spring.cloud.consul.discovery.instanceId 中提供唯一标识符来覆盖此默认行为。例如

application.yml
spring:
  cloud:
    consul:
      discovery:
        instanceId: ${spring.application.name}:${vcap.application.instance_id:${spring.application.instance_id:${random.value}}}

使用此元数据,并且在 localhost 上部署了多个服务实例时,随机值将在这里生效,使实例唯一。在 Cloudfoundry 中,vcap.application.instance_id 会在 Spring Boot 应用程序中自动填充,因此不需要随机值。

查找服务

使用负载均衡器

Spring Cloud 支持 Feign(一个 REST 客户端构建器)以及 Spring RestTemplate,用于通过逻辑服务名称/ID 而非物理 URL 来查找服务。Feign 和支持服务发现的 RestTemplate 都利用 Spring Cloud LoadBalancer 进行客户端负载均衡。

如果您想使用 RestTemplate 访问 STORES 服务,只需声明

@LoadBalanced
@Bean
public RestTemplate loadbalancedRestTemplate() {
     return new RestTemplate();
}

并像这样使用它(请注意我们如何使用 Consul 中的 STORES 服务名称/ID,而不是完全限定域名)

@Autowired
RestTemplate restTemplate;

public String getFirstProduct() {
   return this.restTemplate.getForObject("https://STORES/products/1", String.class);
}

如果您的 Consul 集群分布在多个数据中心,并且您想访问另一个数据中心的服务,仅凭服务名称/ID 是不够的。在这种情况下,您可以使用属性 spring.cloud.consul.discovery.datacenters.STORES=dc-west,其中 STORES 是服务名称/ID,dc-west 是 STORES 服务所在的数据中心。

Spring Cloud 现在也支持 Spring Cloud LoadBalancer

使用 DiscoveryClient

您也可以使用 org.springframework.cloud.client.discovery.DiscoveryClient,它提供了一个简单的、不特定于 Netflix 的发现客户端 API,例如:

@Autowired
private DiscoveryClient discoveryClient;

public String serviceUrl() {
    List<ServiceInstance> list = discoveryClient.getInstances("STORES");
    if (list != null && list.size() > 0 ) {
        return list.get(0).getUri();
    }
    return null;
}

Consul Catalog Watch

Consul Catalog Watch 利用了 Consul 监听服务的能力。Catalog Watch 会进行阻塞的 Consul HTTP API 调用,以确定是否有服务发生变化。如果有新的服务数据,就会发布一个 Heartbeat 事件。

要更改调用 Catalog Watch 的频率,请修改 spring.cloud.consul.discovery.catalog-services-watch-delay。默认值为 1000,单位是毫秒。延迟时间是指上次调用结束到下次调用开始之间的时间间隔。

要禁用 Catalog Watch,请设置 spring.cloud.consul.discovery.catalogServicesWatch.enabled=false

该 Watch 使用 Spring 的 TaskScheduler 来调度对 Consul 的调用。默认是一个 ThreadPoolTaskScheduler,其 poolSize 为 1。要更改 TaskScheduler,请创建一个类型为 TaskScheduler 的 bean,并使用常量 ConsulDiscoveryClientConfiguration.CATALOG_WATCH_TASK_SCHEDULER_NAME 作为其名称。