SpringCloudConsul源码分析(二)服务注册

2,086 阅读5分钟

一、工程结构

二、UML

  • Registration:一个标记接口,继承了ServiceInstance代表一个抽象的服务实例,提供serviceId、host、port、URI、协议、metadata等getter、setter方法。consul对于他的实现就是ConsulRegistration。而ConsulAutoRegistration是Consul自己对于服务注册的扩展,Consul自己的服务实例就是NewService,它Consul自己的metadata、健康检查Check参数设定等等。
  • ServiceRegistry:对于Regisration,提供服务注册、服务注销、服务状态的getter、setter方法。ConsulServiceRegistry是他的实现。
  • AutoServiceRegistration:一个标记接口,它的抽象实现是AbstractAutoServiceRegistration,具体实现是ConsulAutoServiceRegistration。AbstractAutoServiceRegistration类是由spring-cloud-commons提供的。它的start方法提供了服务自动注册的能力,具体如何获取配置、Registration实例由子类实现。
  • ConsulAutoServiceRegistrationListener:实现SmartApplicationListener,利用Spring事件机制的扩展点,触发服务自动注册
  • ConsulRegistrationCustomizer:Consul为ConsulRegistration提供的扩展点。在ConsulRegistration构造完成之后,提供给用户一个扩展ConsulRegistration的接口
public interface ConsulRegistrationCustomizer {
	void customize(ConsulRegistration registration);
}
  • ConsulServiceRegistryAutoConfiguration:ConsulServiceRegistry的配置类
  • ConsulAutoServiceRegistrationAutoConfiguration:ConsulAutoRegistration和ConsulServiceAutoRegistration的配置类

三、从配置讲起

  • spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.consul.serviceregistry.ConsulAutoServiceRegistrationAutoConfiguration,\
org.springframework.cloud.consul.serviceregistry.ConsulServiceRegistryAutoConfiguration\
...

3.1 ConsulServiceRegistryAutoConfiguration

  • 注入条件
// 1、spring.cloud.consul.enabled matchIfMissing = true
// 2、ConsulClient.class存在
@ConditionalOnConsulEnabled
// 1、spring.cloud.service-registry.enabled matchIfMissing = true
// 2、spring.cloud.consul.service-registry.enabled matchIfMissing = true
@Conditional(ConsulServiceRegistryAutoConfiguration.OnConsulRegistrationEnabledCondition.class)
@AutoConfigureBefore(ServiceRegistryAutoConfiguration.class)
public class ConsulServiceRegistryAutoConfiguration {
  • ServiceRegistryAutoConfiguration:spring-cloud-common提供,对服务注册的健康检查
public class ServiceRegistryAutoConfiguration {
	@ConditionalOnBean(ServiceRegistry.class)
	@ConditionalOnClass(Endpoint.class)
	protected class ServiceRegistryEndpointConfiguration {
		@Autowired(required = false)
		private Registration registration;
		@Bean
		@ConditionalOnAvailableEndpoint
		public ServiceRegistryEndpoint serviceRegistryEndpoint(
				ServiceRegistry serviceRegistry) {
			ServiceRegistryEndpoint endpoint = new ServiceRegistryEndpoint(
					serviceRegistry);
			endpoint.setRegistration(this.registration);
			return endpoint;
		}

	}
}
  • ConsulServiceRegistry:直接操作ConsulClient,提供服务注册功能
@Bean
	@ConditionalOnMissingBean
	public ConsulServiceRegistry consulServiceRegistry(ConsulClient consulClient,
			ConsulDiscoveryProperties properties, HeartbeatProperties heartbeatProperties,
			@Autowired(required = false) TtlScheduler ttlScheduler) {
		return new ConsulServiceRegistry(consulClient, properties, ttlScheduler,
				heartbeatProperties);
	}
  • HeartbeatProperties:健康检查的心跳检测方式的配置类
@Bean
	@ConditionalOnMissingBean
	public HeartbeatProperties heartbeatProperties() {
		return new HeartbeatProperties();
	}
  • ConsulDiscoveryProperties:服务注册与发现的相关配置(服务注册和发现的配置混合在一起,官方注释的意思是考虑把注册和发现的配置分开管理更合适
@Bean
	@ConditionalOnMissingBean
	// TODO: Split appropriate values to service-registry for Edgware
	public ConsulDiscoveryProperties consulDiscoveryProperties(InetUtils inetUtils) {
		return new ConsulDiscoveryProperties(inetUtils);
	}

3.2 ConsulAutoServiceRegistrationAutoConfiguration

  • 生效条件
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
@ConditionalOnMissingBean(
		type = "org.springframework.cloud.consul.discovery.ConsulLifecycle")
// 1、spring.cloud.consul.enabled matchIfMissing = true
// 2、ConsulClient.class存在
@ConditionalOnConsulEnabled
// 1、spring.cloud.service-registry.auto-registration.enabled matchIfMissing = true
// 2、spring.cloud.consul.service-registry.auto-registration.enabled matchIfMissing = true
// 3、spring.cloud.service-registry.enabled matchIfMissing = true
// 4、spring.cloud.consul.service-registry.enabled matchIfMissing = true
@Conditional(ConsulAutoServiceRegistrationAutoConfiguration.OnConsulRegistrationEnabledCondition.class)
@AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class,
ConsulServiceRegistryAutoConfiguration.class })
public class ConsulAutoServiceRegistrationAutoConfiguration {
  • 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);
	}
  • ConsulAutoServiceRegistration:实现AbstractAutoServiceRegistration,提供服务注册功能
@Bean
@ConditionalOnMissingBean
public ConsulAutoServiceRegistration consulAutoServiceRegistration(
        ConsulServiceRegistry registry,
        AutoServiceRegistrationProperties autoServiceRegistrationProperties,
        ConsulDiscoveryProperties properties,
        ConsulAutoRegistration consulRegistration) {
    return new ConsulAutoServiceRegistration(registry,
            autoServiceRegistrationProperties, properties, consulRegistration);
}
  • ConsulAutoServiceRegistrationListener:服务自动注册的入口
@Bean
public ConsulAutoServiceRegistrationListener consulAutoServiceRegistrationListener(
        ConsulAutoServiceRegistration registration) {
    return new ConsulAutoServiceRegistrationListener(registration);
}

四、ConsulAutoServiceRegistrationListener何时执行服务注册,通过谁操作服务注册

  • ConsulAutoServiceRegistrationListener作为服务注册的入口,利用了Spring的哪个扩展点?

通过debug发现,ConsulAutoServiceRegistrationListener关注的Event是在Servlet容器启动后触发的,触发入口是:org.springframework.boot.web.reactive.context.WebServerManager#start

 void start() {
        this.handler.initializeHandler();
        // Tomcat启动
        this.webServer.start();
        // 发布ReactiveWebServerInitializedEvent事件,这个事件继承了WebServerInitializedEvent
        this.applicationContext.publishEvent(new ReactiveWebServerInitializedEvent(this.webServer, this.applicationContext));
    }

而ConsulAutoServiceRegistrationListener监听的事件正是WebServerInitializedEvent,从而触发了服务注册流程。

public class ConsulAutoServiceRegistrationListener implements SmartApplicationListener {
	// 监听WebServerInitializedEvent事件
	@Override
	public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
		return WebServerInitializedEvent.class.isAssignableFrom(eventType);
	}
  • 通过谁操作服务注册?

ConsulAutoServiceRegistration实现了AbstractAutoServiceRegistration,主要是提供了服务注册流程上的具体实现,而具体操作Consul服务注册的API那是ConsulServiceRegistry的事情。

public class ConsulAutoServiceRegistrationListener implements SmartApplicationListener {
	private final ConsulAutoServiceRegistration autoServiceRegistration;
    @Override
	public void onApplicationEvent(ApplicationEvent applicationEvent) {
		if (applicationEvent instanceof WebServerInitializedEvent) {
			WebServerInitializedEvent event = (WebServerInitializedEvent) applicationEvent;
            // 通过WebServerInitializedEvent可以获取到WebServer实例,进而获取到启动端口
			this.autoServiceRegistration.setPortIfNeeded(event.getWebServer().getPort());
            // 执行服务注册流程
			this.autoServiceRegistration.start();
		}
	}
}

五、服务注册流程:ConsulAutoServiceRegistration

  • ConsulAutoServiceRegistration的start方法仅仅是调用了抽象父类的start方法,抽象父类的start方法属于一个模板方法,很多东西是靠子类进行实现的
    • 同时注意到之前提到的core模块提供的springRetry拦截器在这里使用了,如果我们引入spring-retry框架就可以按照配置条件执行服务注册的重试了。juejin.cn/post/687154…
	@Override
	@Retryable(interceptor = "consulRetryInterceptor")
	public void start() {
		// 1. 判断ConsulAutoServiceRegistration.isEnabled
		// 2. 发布InstancePreRegisteredEvent事件
		// 3. 调用ConsulAutoServiceRegistration.register
		// 4. 发布InstanceRegisteredEvent事件
		// 5. 设置状态running=true
		super.start();
	}
  • AbstractAutoServiceRegistration.start:主要控制了服务注册的流程,把扩展点都预留出来,不用让子类考虑这些事情,子类只要负责具体服务注册的事情
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
			register();
            // 4. 抽象类控制,发布InstanceRegisteredEvent事件
			this.context.publishEvent(
					new InstanceRegisteredEvent<>(this, getConfiguration()));
            // 5. 抽象类控制,设置已经执行过服务注册了
			this.running.compareAndSet(false, true);
		}
	}
  • 具体看一下ConsulAutoServiceRegistration.register的逻辑,如何实现服务注册
protected void register() {
		// 1. 判断spring.cloud.consul.discovery.register
		if (!this.properties.isRegister()) {
			log.debug("Registration disabled.");
			return;
		}
		// 2. 调用ConsulServiceRegistry注册 自动注入的ConsulAutoRegistration实例
		super.register();
	}
  • 子类仅仅是判断了一下配置是否允许辅助注册,最终实现还是父类AbstractAutoServiceRegistration的register方法,而父类的实现,也仅仅是用注入的ConsulServiceRegistry执行register方法。其中注册的Registration服务实例,是注入的ConsulAutoRegistration(这个东西放到Consul健康检查的时候再讲,目前就当做一个服务实例,包含host、port等必要信息)。
	protected void register() {
		this.serviceRegistry.register(getRegistration());
	}
  • ConsulServiceRegistry.register,主要负责操作ConsulAPI真正执行服务注册
	@Override
	public void register(ConsulRegistration reg) {
		try {
			// 1. http://localhost:8500/v1/agent/service/register 向consul注册服务实例
			// 报文内容
			// {"ID":"testConsulApp-8080","Name":"testConsulApp","Tags":["secure\u003dfalse"],"Address":"192.168.0.105","Meta":{},"Port":8080,"Check":{"Interval":"10s","HTTP":"http://192.168.0.105:8080/actuator/health","Header":{}}}
			this.client.agentServiceRegister(reg.getService(),
					this.properties.getAclToken());
			NewService service = reg.getService();
			// 2. 如果健康检查的方式是心跳方式,则提交一个定时任务,持续向consul agent发送心跳
			if (this.heartbeatProperties.isEnabled() && this.ttlScheduler != null
					&& service.getCheck() != null
					&& service.getCheck().getTtl() != null) {
				this.ttlScheduler.add(reg.getInstanceId());
			}
		} catch (ConsulException e) {
			// 3. 如果是快速失败,直接把异常抛出,否则吃掉异常
			if (this.properties.isFailFast()) {
				ReflectionUtils.rethrowRuntimeException(e);
			}
		}
	}

六、总结

  • 主要理解Registration、ServiceRegistry等的作用职责是什么
  • SpringCloudConsul服务注册利用了Spring容器事件这个扩展点,执行了服务注册
  • SpringCloudConsul服务注册的时机是Servlet容器启动之后,发布WebServerInitializedEvent事件,并且通过这个事件能拿到例如Tomcat等容器实例,从而获得类似端口等属性
  • AbstractAutoServiceRegistration控制服务注册流程,预留扩展点,让子类ConsulAutoServiceRegistration专心实现服务注册
  • /v1/agent/service/register是consul服务注册的api