浅析Spring Cloud Consul(源码分析)
Consul是HashiCorp公司推出的开源工具,用于实现分布式系统的服务发现与配置。Spring Cloud Consul 项目是针对Consul的服务治理实现。项目中有运用到,因此做个简要分析。实际上Spring Cloud已经搭建了一套模版,各个注册服务(Cloud、Eureka等)只需要根据自身的差异进行实现即可。
1. 服务注册
1.1 主要类说明
1.1.1 Registration
Registration和ServiceInstance是由spring-cloud-commons提供了接口。
Registration是一个标记接口,继承了ServiceInstance接口,代表一个抽象的服务实例。ServiceInstace接口,提供了serviceId、host、port等get方法定义。Consul对于Registration的具体实现就是ConsulRegistration,Consul自己的服务实例就是NewService对象,ConsulRegistration实现了ServiceInstace接口方法(内部的实现,是通过从NewService对象上取值)。而ConsulAutoRegistration,是Consul自己对服务自动注册的扩展实例,内部有自己特殊的方式。
1.1.2 AutoServiceRegistration
AutoServiceRegistration和AbstractAutoServiceRegistration,这两个类(或接口)都是spring-cloud-commons提供。
AutoServiceRegistration是一个标记接口,它的抽象实现是AbstractAutoServiceRegistration。AbstractAutoServiceRegistration提供了模板功能,实现了start方法提供服务自动注册能力,实现了register、deregister等protected方法。也提供了getRegistration()、isEnable()等子类需要实现的方法。在Consul中,该抽象类的具体实现就是ConsulAutoServiceRegistration,由该类完成自动注册的一些逻辑判断。
1.1.3 ServiceRegistry
ServiceRegistry接口是spring-cloud-commons提供。
ServiceRegistry是一个接口,对于服务实例Registration,提供了服务注册、服务注销等方法。在Consul中,该类的具体实现是ConsulServiceRegistry。
java
public interface ServiceRegistry<R extends Registration> {
void register(R registration);
void deregister(R registration);
void close();
void setStatus(R registration, String status);
<T> T getStatus(R registration);
}
1.1.4 其他类
1)ConsulAutoServiceRegistrationListener:实现SmartApplicationListener,利用Spring事件机制的扩展点,触发服务自动注册
2)ConsulRegistrationCustomizer:Consul为ConsulRegistration提供的扩展点。
3)ConsulServiceRegistryAutoConfiguration:主要是ConsulServiceRegistry的配置类
4)ConsulAutoServiceRegistrationAutoConfiguration:主要是ConsulAutoRegistration和ConsulServiceAutoRegistration的配置类。
1.2 自动注册
先抛开配置类,结合刚刚分析的各个类,先看下Consul如何实现自动注册。
1)ConsulAutoServiceRegistrationListener类是服务注册的入口,该类实现了SmartApplicationListener接口,实现了具体方法onApplicationEvent。
java
//ConsulAutoServiceRegistrationListener#onApplicationEvent
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
if (applicationEvent instanceof WebServerInitializedEvent) {
WebServerInitializedEvent event = (WebServerInitializedEvent) applicationEvent;
ApplicationContext context = event.getApplicationContext();
if (context instanceof ConfigurableWebServerApplicationContext) {
if ("management"
.equals(((ConfigurableWebServerApplicationContext) context)
.getServerNamespace())) {
return;
}
}
//通过WebServerInitializedEvent可以获取到WebServer实例,进而获取到启动端口
this.autoServiceRegistration.setPortIfNeeded(event.getWebServer().getPort());
//执行自动注册逻辑
this.autoServiceRegistration.start();
}
}
从上述代码,可以看出,关注的Event是WebServerInitializedEvent,该事件是在Servlet容器启动后触发。在onApplicationEvent方法中,会调用ConsulAutoServiceRegistration的start方法。
2)ConsulAutoServiceRegistration的start方法,仅调用抽象父类的start方法
java
//ConsulAutoServiceRegistration#start
@Override
@Retryable(interceptor = "consulRetryInterceptor")
public void start() {
super.start();
}
3)AbstractAutoServiceRegistration的start方法,主要是控制服务注册的流程。把扩展点预留,让子类实现。
java
//AbstractAutoServiceRegistration#start
public void start() {
//抽象方法,子类实现
if (!this.isEnabled()) {
if (logger.isDebugEnabled()) {
logger.debug("Discovery Lifecycle disabled. Not starting");
}
} else {
if (!this.running.get()) {
//发布InstancePreRegisteredEvent事件
this.context.publishEvent(new InstancePreRegisteredEvent(this, this.getRegistration()));
//protected方法,子类可以覆盖抽闲类方法。
this.register();
if (this.shouldRegisterManagement()) {
this.registerManagement();
}
//发布InstanceRegisteredEvent事件
this.context.publishEvent(new InstanceRegisteredEvent(this, this.getConfiguration()));
this.running.compareAndSet(false, true);
}
}
}
4)重点看下ConsulAutoServiceRegistration如何重写方法。isEnabled()取决于配置中的lifecycle(配置spring.cloud.consul.discovery.lifecycle.enable),该对象默认为true。register()方法中增加了一层配置判断,spring.cloud.consul.discovery.register,该配置默认为true,然后调用父类的register方法。AbstractAutoServiceRegistration中的register方法,则是调用ServiceRegistry接口中的register方法,进行实际注册。在Consul中就是ConsulServiceRegistry
java
//ConsulAutoServiceRegistration#start
@Override
protected boolean isEnabled() {
return this.properties.getLifecycle().isEnabled();
}
java
//ConsulAutoServiceRegistration#register
@Override
protected void register() {
if (!this.properties.isRegister()) {
log.debug("Registration disabled.");
return;
}
super.register();
}
java
//AbstractAutoServiceRegistration#register
protected void register() {
this.serviceRegistry.register(this.getRegistration());
}
5)getRegistration()具体在ConsulAutoServiceRegistration方法里实现,返回一个ConsulAutoRegistration对象。
java
//ConsulAutoServiceRegistration#getRegistration
@Override
protected ConsulAutoRegistration getRegistration() {
if (this.registration.getService().getPort() == null
&& this.getPort().get() > 0) {
this.registration.initializePort(this.getPort().get());
}
Assert.notNull(this.registration.getService().getPort(),
"service.port has not been set");
return this.registration;
}
6)ConsulServiceRegistry调用register方法,进行实际的注册逻辑。主要是想consul注册服务实例。
java
//ConsulServiceRegistry#register
@Override
public void register(ConsulRegistration reg) {
log.info("Registering service with consul: " + reg.getService());
try {
//向consul注册服务实例
this.client.agentServiceRegister(reg.getService(),
this.properties.getAclToken());
NewService service = reg.getService();
//如果健康检查的方式是心跳,则提交一个定时任务,持续向consul agent发送。
if (this.heartbeatProperties.isEnabled() && this.ttlScheduler != null
&& service.getCheck() != null
&& service.getCheck().getTtl() != null) {
this.ttlScheduler.add(reg.getInstanceId());
}
}
catch (ConsulException e) {
//如果快速失败,直接把异常抛出。
if (this.properties.isFailFast()) {
log.error("Error registering service with consul: " + reg.getService(),
e);
ReflectionUtils.rethrowRuntimeException(e);
}
log.warn("Failfast is false. Error registering service with consul: "
+ reg.getService(), e);
}
}
1.3 自动注销
自动注销,则是依赖于@PreDestroy注解,在bean结束生命周期的时候,通过ServiceRegistry取消注册。具体代码如下
java
//AbstractAutoServiceRegistration#destroy
@PreDestroy
public void destroy() {
stop();
}
java
//AbstractAutoServiceRegistration#stop
public void stop() {
if (this.getRunning().compareAndSet(true, false) && isEnabled()) {
deregister();
if (shouldRegisterManagement()) {
deregisterManagement();
}
this.serviceRegistry.close();
}
}
java
//AbstractAutoServiceRegistration#deregister
protected void deregister() {
this.serviceRegistry.deregister(getRegistration());
}
1.4 自动配置
服务注册之所以能这么方便,是因为用了Spring-boot的自动配置机制。详细如下
spring.factories中配置了,注入的Bean
ini
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.consul.serviceregistry.ConsulAutoServiceRegistrationAutoConfiguration,\
org.springframework.cloud.consul.serviceregistry.ConsulServiceRegistryAutoConfiguration,\
......
1.4.1 ConsulServiceRegistryAutoConfiguration
-
@ConditionalOnConsulEnabled是spring-cloud-consul-core提供的注解,要求两点:spring.cloud.consul.enabled为true(默认为true);ConsulClient.class类存在
-
@Conditional后面的的要求有两点:spring.cloud.service-registry.enabled为true(默认为true);spring.cloud.consul.service-registry.enabled为true(默认为true)。
-
@AutoConfigureBefore的意思是:ConsulServiceRegistryAutoConfiguration在ServiceRegistryAutoConfiguration之前加载。
java
@Configuration(proxyBeanMethods = false)
@ConditionalOnConsulEnabled
@Conditional(ConsulServiceRegistryAutoConfiguration.OnConsulRegistrationEnabledCondition.class)
@AutoConfigureBefore(ServiceRegistryAutoConfiguration.class)
public class ConsulServiceRegistryAutoConfiguration
另外ConsulServiceRegistryAutoConfiguration注册了三个Bean(在没有该Bean的情况下),
-
ConsulServiceRegistry:直接操作ConsulClient,提供服务注册功能。实现ServiceRegistry接口。
-
HeartbeatProperties:健康检查的心跳检测方式的配置类
-
ConsulDiscoveryProperties:服务注册与发现的相关配置。
1.4.2 ConsulAutoServiceRegistrationAutoConfiguration
-
@ConditionalOnConsulEnable前面已经提到过了。
-
@Conditional后面的要求有4点,要求同时满足:
- spring.cloud.service-registry.auto-registration.enabled配置为true。(默认为true)
- spring.cloud.consul.service-registry.auto-registration.enabled配置为true。(默认为true)
- spring.cloud.service-registry.enabled配置为true。(默认为true)
- spring.cloud.consul.service-registry.enabled配置为true。(默认为true)
-
@AutoConfigureAfter表示,ConsulAutoServiceRegistrationAutoConfiguration在AutoServiceRegistrationConfiguration和ConsulServiceRegistryAutoConfiguration之后加载。
java
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
@ConditionalOnMissingBean(
type = "org.springframework.cloud.consul.discovery.ConsulLifecycle")
@ConditionalOnConsulEnabled
@Conditional(ConsulAutoServiceRegistrationAutoConfiguration.OnConsulRegistrationEnabledCondition.class)
@AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class,
ConsulServiceRegistryAutoConfiguration.class })
public class ConsulAutoServiceRegistrationAutoConfiguration
另外ConsulAutoServiceRegistrationAutoConfiguration注册了三个Bean
-
ConsulAutoRegistration:代表当前服务实例。
-
ConsulAutoServiceRegistration:提供服务注册功能。实现了AbstractAutoServiceRegistration抽象类。
-
ConsulAutoServiceRegistrationListener:服务自动注册的入口。实现了SmartApplicationListener接口。
2. 服务发现
2.1 DiscoveryClient实现
DiscoveryClient是spring-cloud-common提供了的一个接口。核心就两个方法
java
//DiscoveryClient
public interface DiscoveryClient extends Ordered {
//根据serviceId查询所有服务实例
List<ServiceInstance> getInstances(String serviceId);
//查询所有serviceId
List<String> getServices();
}
Consul对于该接口的实现,就是ConsulDiscoveryClient
java
//ConsulDiscoveryClient#getInstances
@Override
public List<ServiceInstance> getInstances(final String serviceId) {
return getInstances(serviceId,
new QueryParams(this.properties.getConsistencyMode()));
}
java
//ConsulDiscoveryClient#getServices
@Override
public List<String> getServices() {
String aclToken = this.properties.getAclToken();
CatalogServicesRequest request = CatalogServicesRequest.newBuilder()
.setQueryParams(QueryParams.DEFAULT)
.setToken(this.properties.getAclToken()).build();
return new ArrayList<>(
this.client.getCatalogServices(request).getValue().keySet());
}
2.2 结合Ribbon实现
1)ConsulServer。继承com.netflix.loadbalancer的Server类。
2)ConsulServerList。继承com.netflix.loadbalancer的抽象类AbstractServerList类,实现ServerList接口。
3)HealthServiceServerListFilter:实现com.netflix.loadbalancer的ServerListFilter接口。
4)ConsulPing:实现com.netflix.loadbalancer的IPing接口。
5)ConsulServerIntrospector。继承org.springframework.cloud.netflix.ribbon的DefaultServerIntrospector类
2.3 其他
ConsulCatalogWatch:启动一个定时任务,每秒更新consulIndex,并且发送HeartbeatEvent事件。暂不清楚,这个类的意义。
3. 健康监测
3.1 Consul健康检查方式
ConsulAgent负责健康检查工作,主要有以下几种方式:
1)Script + Interval:定时执行脚本,根据脚本返回码确定应用是否存活,0表示存活,1表示警告,其他表示死亡。
2)HTTP + Interval:定时向应用某个端点发送http请求,根据http状态码确认应用是否存活,2xx表示存活。
3)TCP + Interval:定时发起tcp连接,超时时间内连接上表示存活。
4)Time To Live(TTL):应用在过期时间内,通过http的方式主动发送心跳给ConsulAgent,超过过期时间没有收到心跳表示死亡。
5)Docker + Interval:定时执行Docket exec命令,通过shell脚本的方式根据返回码确认应用是否存活。
6)gRPC +Interval : 定时通过gRPC协议请求配置的端点,检测是否存活。
对于Spring Cloud Consul 来说,支持两种方式
- HTTP+ Interval
- Time To Live(TTL)
无论哪种方式,都是在首次调用Consul执行服务注册的报文中确定的。
3.2 Check方式确认
在自动注册的ConsulRegistration的NewService中,包含了Check信息,在生成ConsulAutoRegistration时确认。
java
//ConsulAutoRegistration#setCheck
public static void setCheck(NewService service,
AutoServiceRegistrationProperties autoServiceRegistrationProperties,
ConsulDiscoveryProperties properties, ApplicationContext context,
HeartbeatProperties heartbeatProperties) {
if (properties.isRegisterHealthCheck() && service.getCheck() == null) {
Integer checkPort;
if (shouldRegisterManagement(autoServiceRegistrationProperties, properties,
context)) {
checkPort = getManagementPort(properties, context);
}
else {
checkPort = service.getPort();
}
Assert.notNull(checkPort, "checkPort may not be null");
service.setCheck(createCheck(checkPort, heartbeatProperties, properties));
}
}
主要逻辑在createCheck中。
java
//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());
}
//spring.cloud.consul.discovery.heartbeat.enable为true(默认为false),采用TTL方式进行健康检查
if (ttlConfig.isEnabled()) {
//默认ttl为30s
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");
//否则采用http + interval 的健康检查方式,consulAgent定时请求应用http端点,返回200表示健康
//如果自定义了healthCechkUrl,使用自定义的健康检查接口
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());
//检查间隔时间,默认10s
check.setInterval(properties.getHealthCheckInterval());
//检查超时时间
check.setTimeout(properties.getHealthCheckTimeout());
check.setTlsSkipVerify(properties.getHealthCheckTlsSkipVerify());
return check;
}
3.3 TTL实现
TTL的实现方式是在注册结束后,把服务id封装为ConsulHeartbeatTask放到线程池,持续请求consul发送心跳。
在ConsulServiceRegistry注册是,如果健康检查方式是ttl的话,则提交一个定时任务,持续向consul agent发送心跳。
java
//ConsulServiceRegistry#register
if (this.heartbeatProperties.isEnabled() && this.ttlScheduler != null
&& service.getCheck() != null
&& service.getCheck().getTtl() != null) {
this.ttlScheduler.add(reg.getInstanceId());
}
}
TtlScheduler把instanceId封装为ConsulHeartbeatTask(实现Runnable接口),通过scheduler以一定的时间间隔发送心跳。
java
//TtlScheduler#add
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);
}
}
在ConsulHeartbeatTask的任务里,想ConsulAgent发送心跳。
java
//TtlScheduler$ConsulHeartbeatTask#run
@Override
public void run() {
TtlScheduler.this.client.agentCheckPass(this.checkId);
if (log.isDebugEnabled()) {
log.debug("Sending consul heartbeat for: " + this.checkId);
}
}
4. 参考资料
1)Spring-cloud-consul代码分支:2.2.x
2)SpringCloudConsul源码分析(二)服务注册 juejin.cn/post/687189...
3)SpringCloudConsul源码分析(三)服务发现 juejin.cn/post/687223...
4)SpringCloudConsul源码分析(四)健康检查 juejin.cn/post/687844...