一、Consul健康检查的几种方式
ConsulAgent负责健康检查工作,主要有下面几种方式
- Script + Interval:定时执行脚本,根据脚本返回码确定应用是否存活,0表示存活,1表示警告,其他表示死亡。
- HTTP + Interval:定时向应用某个端点发送http请求,根据http状态码确定应用是否存活,2xx表示存活。
- TCP + Interval:定时发起tcp连接,超时时间内连接上表示存活。
- Time to Live (TTL):应用在过期时间内,通过http的方式主动发送心跳给ConsulAgent,超过过期时间没有收到心跳表示死亡。
- Docker + Interval:定时执行Docker exec命令,通过shell脚本的方式根据返回码确定应用是否存活。
- gRPC + Interval:定时通过gRPC协议请求配置的端点,检测是否存活。
对于SpringCloudConsul来说,支持两种方式
- HTTP + Interval
- Time to Live (TTL)
无论是哪种方式,都是在首次调用Consul执行服务注册的报文中确定的。
二、服务注册回顾
先回顾一下,服务注册的入口是ConsulAutoServiceRegistrationListener,当Tomcat容器启动后,这里收到WebServerInitializedEvent,执行服务注册。
/**
* 这里触发了服务注册,这个时间点是
* 容器实例化所有单例bean后,finishRefresh阶段
* org.springframework.context.support.AbstractApplicationContext#finishRefresh
* 调用所有SmartLifecycle的bean的start方法
* org.springframework.context.support.DefaultLifecycleProcessor#onRefresh
* 调用WebServerStartStopLifecycle的start方法,servlet容器启动之后
* org.springframework.boot.web.servlet.context.WebServerStartStopLifecycle#start
* @param applicationEvent
*/
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
if (applicationEvent instanceof WebServerInitializedEvent) {
WebServerInitializedEvent event = (WebServerInitializedEvent) applicationEvent;
ApplicationContext context = event.getApplicationContext();
this.autoServiceRegistration.setPortIfNeeded(event.getWebServer().getPort());
this.autoServiceRegistration.start();
}
}
复制代码
- autoServiceRegistration对应的是ConsulAutoServiceRegistration实例,它的start方法的流程是由父类规定的
ConsulAutoServiceRegistration
public void start() {
super.start();
}
复制代码
- 父类AbstractAutoServiceRegistration由spring-cloud-common提供抽象注册流程
public void start() {
// 1. 判断ConsulAutoServiceRegistration.isEnabled
if (!isEnabled()) {
return;
}
if (!this.running.get()) {
// 2. 发布InstancePreRegisteredEvent事件
this.context.publishEvent(
new InstancePreRegisteredEvent(this, getRegistration()));
// 3. 调用ConsulAutoServiceRegistration.register
// 实际是调用AbstractAutoServiceRegistration#register
register();
// 4. 发布InstanceRegisteredEvent事件
this.context.publishEvent(
new InstanceRegisteredEvent<>(this, getConfiguration()));
// 5. 设置状态running=true
this.running.compareAndSet(false, true);
}
}
复制代码
- AbstractAutoServiceRegistration#register
调用ConsulServiceRegistry的register方法(http://localhost:8500/v1/agent/service/register)直接注册一个ConsulAutoRegistration实例
protected void register() {
this.serviceRegistry.register(getRegistration());
}
复制代码
这里getRegistration()是获取自动注入的ConsulAutoRegistration实例,关键点就是ConsulAutoRegistration里的数据组成的报文
@Bean
@ConditionalOnMissingBean
public ConsulAutoRegistration consulRegistration(
AutoServiceRegistrationProperties autoServiceRegistrationProperties,
ConsulDiscoveryProperties properties, ApplicationContext applicationContext,
ObjectProvider<List<ConsulRegistrationCustomizer>> registrationCustomizers,
ObjectProvider<List<ConsulManagementRegistrationCustomizer>> managementRegistrationCustomizers,
HeartbeatProperties heartbeatProperties) {
return ConsulAutoRegistration.registration(autoServiceRegistrationProperties,
properties, applicationContext, registrationCustomizers.getIfAvailable(),
managementRegistrationCustomizers.getIfAvailable(), heartbeatProperties);
}
复制代码
三、ConsulAutoRegistration
ConsulAutoRegistration封装了NewService,而NewService是consul中对于服务实例的抽象,是向Consul注册的数据报文来源。
ConsulAutoRegistration.registration:构建ConsulAutoRegistration
public static ConsulAutoRegistration registration(
AutoServiceRegistrationProperties autoServiceRegistrationProperties,
ConsulDiscoveryProperties properties, ApplicationContext context,
List<ConsulRegistrationCustomizer> registrationCustomizers,
List<ConsulManagementRegistrationCustomizer> managementRegistrationCustomizers,
HeartbeatProperties heartbeatProperties) {
// NewService是consul中对于服务实例的抽象
NewService service = new NewService();
// 优先spring.cloud.consul.discovery.serviceName
// 否则spring.application.name
// 默认application
String appName = getAppName(properties, context.getEnvironment());
// instanceId
service.setId(getInstanceId(properties, context));
if (!properties.isPreferAgentAddress()) {
// return this.preferIpAddress ? this.ipAddress : this.hostname
service.setAddress(properties.getHostname());
}
service.setName(normalizeForDns(appName));
service.setTags(createTags(properties));
service.setEnableTagOverride(properties.getEnableTagOverride());
service.setMeta(getMetadata(properties));
if (properties.getPort() != null) {
service.setPort(properties.getPort());
// 设置Check属性,表示consul健康检查的方式
setCheck(service, autoServiceRegistrationProperties, properties, context,
heartbeatProperties);
}
ConsulAutoRegistration registration = new ConsulAutoRegistration(service,
autoServiceRegistrationProperties, properties, context,
heartbeatProperties, managementRegistrationCustomizers);
// 触发所有ConsulRegistrationCustomizer的customize方法,一般是给consul注册服务实例打tag
customize(registrationCustomizers, registration);
return registration;
}
复制代码
setCheck方法:
public static void setCheck(NewService service,
AutoServiceRegistrationProperties autoServiceRegistrationProperties,
ConsulDiscoveryProperties properties, ApplicationContext context,
HeartbeatProperties heartbeatProperties) {
// spring.cloud.consul.discovery.registerHealthCheck = true
if (properties.isRegisterHealthCheck() && service.getCheck() == null) {
Integer checkPort = service.getPort();
service.setCheck(createCheck(checkPort, heartbeatProperties, properties));
}
}
复制代码
createCheck方法:设置NewService.Check
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());
}
// 1.如果spring.cloud.consul.discovery.heartbeat.enabled=true(默认是false),采用ttl方式进行健康检查
if (ttlConfig.isEnabled()) {
// 默认ttl是30s
check.setTtl(ttlConfig.getTtl());
return check;
}
// 2. 否则采用http+interval的健康检查方式,consulAgent定时请求应用http端点,返回200表示健康
// 如果自定义了healthCheckUrl,使用自定义的健康检查接口
if (properties.getHealthCheckUrl() != null) {
check.setHttp(properties.getHealthCheckUrl());
}
// 否则使用springboot-actuator自带的/actuator/health
else {
check.setHttp(String.format("%s://%s:%s%s", properties.getScheme(),
properties.getHostname(), port, properties.getHealthCheckPath()));
}
check.setHeader(properties.getHealthCheckHeaders());
// 检查间隔时间
check.setInterval(properties.getHealthCheckInterval());
// 检查超时时间
check.setTimeout(properties.getHealthCheckTimeout());
check.setTlsSkipVerify(properties.getHealthCheckTlsSkipVerify());
return check;
}
复制代码
四、TTL健康检查的实现方式
ttl的实现方式是在注册结束后,把服务id封装为ConsulHeartbeatTask放入线程池,持续请求consul发送心跳
- ConsulServiceRegistry#register注册并启动心跳
// 1. http://localhost:8500/v1/agent/service/register 向consul注册服务实例
this.client.agentServiceRegister(reg.getService(),
this.properties.getAclToken());
NewService service = reg.getService();
// 2. 如果健康检查的方式是ttl方式,则提交一个定时任务,持续向consul agent发送心跳
// spring.cloud.consul.discovery.heartbeat.enable=true
if (this.heartbeatProperties.isEnabled() && this.ttlScheduler != null
&& service.getCheck() != null
&& service.getCheck().getTtl() != null) {
this.ttlScheduler.add(reg.getInstanceId());
}
复制代码
- ConsulHeartbeatAutoConfiguration注入了TtlScheduler
@Bean
@ConditionalOnMissingBean
public TtlScheduler ttlScheduler(HeartbeatProperties heartbeatProperties,
ConsulClient consulClient) {
return new TtlScheduler(heartbeatProperties, consulClient);
}
复制代码
- ttlScheduler.add(String instanceId)把instanceId封装为ScheduledFuture放入线程池定时执行
public void add(String instanceId) {
ScheduledFuture task = this.scheduler.scheduleAtFixedRate(
new ConsulHeartbeatTask(instanceId), this.configuration
.computeHeartbeatInterval().toStandardDuration().getMillis());
ScheduledFuture previousTask = this.serviceHeartbeats.put(instanceId, task);
if (previousTask != null) {
previousTask.cancel(true);
}
}
复制代码
private class ConsulHeartbeatTask implements Runnable {
private String checkId;
ConsulHeartbeatTask(String serviceId) {
this.checkId = serviceId;
if (!this.checkId.startsWith("service:")) {
this.checkId = "service:" + this.checkId;
}
}
/**
* 调用http://localhost:8500/v1/agent/check/pass/service:testConsulApp-8080向consul发送心跳
*/
@Override
public void run() {
TtlScheduler.this.client.agentCheckPass(this.checkId);
}
}
复制代码
五、总结
- SpringCloudConsul支持两种健康检查方式:1)http+interval 2)TTL。
- SpringCloudConsul默认使用http+interval,默认使用springboot-actuator自带的/actuator/health端点,可以通过设置spring.cloud.consul.discovery.healthCheckUrl设置自定义的端点。
- 通过设置spring.cloud.consul.discovery.heartbeat.enable=true可以将健康检查方式设置为TTL,由应用主动向consul发送心跳,过期时间默认30s,可以通过设置spring.cloud.consul.discovery.heartbeat.ttlValue来改变过期时间。
- 服务注册:
PUT
http://localhost:8500/v1/agent/service/register
{
"ID": "testConsulApp-8080",
"Name": "testConsulApp",
"Tags": [
"secure=false"
],
"Address": "127.0.0.1",
"Meta": {},
"Port": 8080,
"Check": {
"TTL": "30s"
}
}
复制代码
- 服务注销:
PUT
http://localhost:8500/v1/agent/service/deregister/testConsulApp-8080
近期评论