前言

我们知道Eureka是通过Client向Server发送renew通知来续命,属于是"去中心化"的设计,而Consul是"中心化"设计,Consul的心跳由Server端发起

Consul心跳

Client在注册到Consul Server的时候(ConsulServiceRegistry#register),会将客户端的注册信息全部发送给注册中心(接口:/v1/agent/service/register),其中主要信息包括服务id、name、ip、port、health-check-url等,所以Consul Server才会知道向Client的哪个接口发送心跳。

{
    id='consul-demo-7702',
    name='consul-demo',
    tags=[
        secure=false
    ],
    address='192.168.0.107',
    meta=null,
    port=7702,
    enableTagOverride=null,
    check=Check{
        script='null',
        interval='10s',
        ttl='null',
        http='http://192.168.0.107:7702/health',
        method='null',
        header={
        },
        tcp='null',
        timeout='null',
        deregisterCriticalServiceAfter='null',
        tlsSkipVerify=null,
        status='null'
    },
    checks=null
}

这里check.http是值是我们在项目的properties文件中配置的:

server.port=7702
spring.application.name=consul-demo
spring.cloud.consul.host=127.0.0.1
spring.cloud.consul.port=8500
# 心跳接口
spring.cloud.consul.discovery.health-check-path=/health

Spring Cloud Consul的心跳接口默认为actuator包中的/actuator/health,所以如果我们既没设置自定义的心跳接口,也没依赖actuator包,那么Consul Server就会在我们注册的Service上显示service checks fail

image-20200412013415372

我们查看Consul Server控制台,发现会控制台健康检查语句agent: Check is now critical: check=service:consul-demo-client-18090,正常的健康检查语句是agent: Check status updated: check=service:consul-demo-7702 status=passing

默认情况下Consul会每隔10秒,通过一个HTTP接口/health来检测节点的健康情况。 如果健康检测失败,那服务实例就会被标记成critical,可以通过在检查定义中指定超时字段来配置自定义HTTP检查超时值,检查的输出限制在大约4KB,大于此值的响应将被截断,会被认为健康检查未通过。

spring.cloud.consul.discovery.prefer-ip-address参数决定上报给注册中心的健康接口是IP还是hostname

  • 健康检查接口创建源码

    ConsulAutoRegistration#createCheck

public static NewService.Check createCheck(Integer port,
			HeartbeatProperties ttlConfig, ConsulDiscoveryProperties properties) {
  NewService.Check check = new NewService.Check();
  if (StringUtils.hasText(properties.getHealthCheckCriticalTimeout())) {
    check.setDeregisterCriticalServiceAfter(
      properties.getHealthCheckCriticalTimeout());
  }
  if (ttlConfig.isEnabled()) {
    check.setTtl(ttlConfig.getTtl());
    return check;
  }

  Assert.notNull(port, "createCheck port must not be null");
  Assert.isTrue(port > 0, "createCheck port must be greater than 0");

  // 若自定义了spring.cloud.consul.discovery.health-check-url
  if (properties.getHealthCheckUrl() != null) {
    check.setHttp(properties.getHealthCheckUrl());
  } else {
    // 自定义了spring.cloud.consul.discovery.health-check-path或默认
    check.setHttp(String.format("%s://%s:%s%s", properties.getScheme(),
                                properties.getHostname(), port, properties.getHealthCheckPath()));
  }
  // spring.cloud.consul.discovery.health-check-headers
  check.setHeader(properties.getHealthCheckHeaders());
  // 设置健康检查频率spring.cloud.consul.discovery.health-check-interval,字符串,要加上单位"5s"
  check.setInterval(properties.getHealthCheckInterval());
  // 设置健康检查超时时间spring.cloud.consul.discovery.health-check-timeout,字符串,要加上单位"5s"
  check.setTimeout(properties.getHealthCheckTimeout());
  check.setTlsSkipVerify(properties.getHealthCheckTlsSkipVerify());
  return check;
}

image-20200412020911482

图中Tags一栏有一个secure=false,这个是由客户端返回给Server,这个标识是检测健康检查接口是否为https协议

public static List<String> createTags(ConsulDiscoveryProperties properties) {
  List<String> tags = new LinkedList<>(properties.getTags());

  if (!StringUtils.isEmpty(properties.getInstanceZone())) {
    tags.add(properties.getDefaultZoneMetadataName() + "="
             + properties.getInstanceZone());
  }
  if (!StringUtils.isEmpty(properties.getInstanceGroup())) {
    tags.add("group=" + properties.getInstanceGroup());
  }

  // 检查请求schema是否为https
  tags.add("secure="
           + Boolean.toString(properties.getScheme().equalsIgnoreCase("https")));

  return tags;
}
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐