68. Consul 服务发现

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

68.1 如何激活

要激活 Consul 服务发现,请使用带有 group org.springframework.cloud 和 artifact id spring-cloud-starter-consul-discovery 的 starter。请参见 Spring Cloud 项目页面,了解有关使用当前 Spring Cloud 发布列车设置构建系统的详细信息。

68.2 向 Consul 注册

当客户端向 Consul 注册时,它提供有关自身的元数据,如主机和端口、ID、名称和标记。默认情况下,会创建一个 HTTP 检查,即 consul 每 10 秒访问 /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

[Caution] 警告

如果使用 Spring Cloud Consul 配置,则需要将上述值放在 bootstrap.yml 中而不是 application.yml 中。

从环境中获取的默认服务名称、实例 ID 和端口分别为 ${spring.application.name}、Spring Context ID 和 ${server.port}。

要禁用 Consul 发现客户端,可以将 spring.cloud.consul.discovery.enabled 设置为 false。

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

68.2.1 将管理注册为单独的服务

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

application.yml. 

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

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

  • Application Service:
ID: myApp
Name: myApp
  • Management Service:
ID: myApp-management
Name: myApp-management

管理服务将从应用程序服务继承其 instanceId 和 serviceName。例如:

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 个服务:

  • Application Service:
ID: custom-service-id
Name: myprefix-myApp
  • Management Service:
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

68.3 HTTP 健康检查

Consul 实例的健康检查默认为 "/health",这是 Spring Boot 执行器应用程序中有用端点的默认位置。如果使用非默认上下文路径或 servlet 路径(例如 server.servletPath=/foo)或管理终结点路径(例如 management.server.servlet.context-path=/admin),则即使对于执行器应用程序,也需要更改这些路径。也可以配置 Consul 用于检查健康端点的间隔。"10s" 和 "1m" 分别代表 10 秒和 1 分钟。示例:

application.yml. 

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

你可以通过设置 management.health.consul.enabled=false 禁用健康检查。

68.3.1 元数据和 Consul 标记

Consul 还不支持服务上的元数据。Spring Cloud 的 ServicServiceInstanceeInstance 有一个 Map<String, String> metadata 字段。Spring Cloud Consul 使用 Consul 标记来近似元数据,直到 Consul 正式支持元数据。格式为 key=value 的标记将被拆分并分别用作 Map 键和值。不带 = 符号的标记将同时用作键和值。

application.yml. 

spring:
  cloud:
    consul:
      discovery:
        tags: foo=bar, baz

上述配置将生成一个带有 foo→bar 和 baz→baz 的映射。

68.3.2 使 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}}}

有了这个元数据,以及在本地主机上部署的多个服务实例,随机值就会出现,使实例具有唯一性。在 CloudFoundry 中,vcap.application.instance_id 将在 Spring Boot 应用程序中自动填充,因此不需要随机值。

68.3.3 将头应用于健康检查请求

头可以应用于健康检查请求。例如,如果你尝试注册使用 Vault 后端的 Spring 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"

68.4 查找服务

68.4.1 使用 Ribbon

Spring Cloud 支持 Feign(一个 REST 客户端构建器)和 Spring RestTemplate,用于使用逻辑服务名/ID而不是物理 URL 查找服务。Feign 和 discovery-aware RestTemplate 都利用 Ribbon 实现客户端负载平衡。

如果要使用 RestTemplate 访问服务存储,只需声明:

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

并按如下方式使用(请注意我们如何使用 Consul 提供的 STORES 服务 name/id 而不是完全限定的域名):

@Autowired
RestTemplate restTemplate;

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

如果你在多个数据中心中有 Consul 集群,并且希望访问另一个数据中心中的服务,那么仅服务 name/id 是不够的。在这种情况下,你使用属性 spring.cloud.consul.discovery.datacenters.STORES=dc-west,其中 STORES 是服务 name/id,dc-west 是 STORES 服务所在的数据中心。

68.4.2 使用 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;
}

68.5 Consul Catalog Watch

Consul Catalog Watch 利用 Consul watch service 的能力。Catalog Watch 进行一个阻塞的 Consul HTTP API 调用,以确定是否有任何服务发生了更改。如果有新的服务数据,则会发布心跳事件。

更改配置监视被称为更改 spring.cloud.consul.config.discovery.catalog-services-watch-delay 时的频率。默认值为 1000,单位为毫秒。延迟是上一次调用结束后和下一次调用开始后的时间量。

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

watch 使用 Spring TaskScheduler 来安排对 Consul 的呼叫。默认情况下,它是池大小为 1 的 ThreadPoolTaskScheduler。要更改 TaskScheduler,请创建一个类型为 TaskScheduler 的 bean,该 bean 使用 ConsulDiscoveryClientConfiguration.CATALOG_WATCH_TASK_SCHEDULER_NAME 常量命名。