Consul心跳机制

前言

我们知道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的哪个接口发送心跳。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
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文件中配置的:

1
2
3
4
5
6
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
}