接下来阅读SpringBoot项目启动时Nacos客户端将自己这个实例注册到Nacos服务端的流程。

程序的入口

我们在使用Nacos作为注册中心时,会在pom.xml添加如下依赖:

<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

在依赖中查看自动装配文件spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.alibaba.cloud.nacos.discovery.NacosDiscoveryAutoConfiguration,\
  com.alibaba.cloud.nacos.endpoint.NacosDiscoveryEndpointAutoConfiguration,\
  com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration,\
  com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration,\
  com.alibaba.cloud.nacos.discovery.reactive.NacosReactiveDiscoveryClientConfiguration,\
  com.alibaba.cloud.nacos.discovery.configclient.NacosConfigServerAutoConfiguration,\
  com.alibaba.cloud.nacos.loadbalancer.LoadBalancerNacosAutoConfiguration,\
  com.alibaba.cloud.nacos.NacosServiceAutoConfiguration,\
  com.alibaba.cloud.nacos.utils.UtilIPv6AutoConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
  com.alibaba.cloud.nacos.discovery.configclient.NacosDiscoveryClientConfigServiceBootstrapConfiguration
org.springframework.context.ApplicationListener=\
  com.alibaba.cloud.nacos.discovery.logging.NacosLoggingListener

注入的对象很多,从类名可以看出NacosServiceRegistryAutoConfiguration就是我们要看的重点了。

NacosServiceRegistryAutoConfiguration

NacosServiceRegistryAutoConfiguration注入了3个Bean:

  • NacosServiceRegistry:负责具体的服务注册。
  • NacosRegistration:收集当前实例的信息,如端口、IP、服务名等。
  • NacosAutoServiceRegistration:管理服务自动注册的时机。
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnNacosDiscoveryEnabled
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled",
		matchIfMissing = true)
@AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class,
		AutoServiceRegistrationAutoConfiguration.class,
		NacosDiscoveryAutoConfiguration.class })
public class NacosServiceRegistryAutoConfiguration {

	@Bean
	public NacosServiceRegistry nacosServiceRegistry(
			NacosServiceManager nacosServiceManager,
			NacosDiscoveryProperties nacosDiscoveryProperties) {
		return new NacosServiceRegistry(nacosServiceManager, nacosDiscoveryProperties);
	}

	@Bean
	@ConditionalOnBean(AutoServiceRegistrationProperties.class)
	public NacosRegistration nacosRegistration(
			ObjectProvider<List<NacosRegistrationCustomizer>> registrationCustomizers,
			NacosDiscoveryProperties nacosDiscoveryProperties,
			ApplicationContext context) {
		return new NacosRegistration(registrationCustomizers.getIfAvailable(),
				nacosDiscoveryProperties, context);
	}

	@Bean
	@ConditionalOnBean(AutoServiceRegistrationProperties.class)
	public NacosAutoServiceRegistration nacosAutoServiceRegistration(
			NacosServiceRegistry registry,
			AutoServiceRegistrationProperties autoServiceRegistrationProperties,
			NacosRegistration registration) {
		return new NacosAutoServiceRegistration(registry,
				autoServiceRegistrationProperties, registration);
	}

}
NacosAutoServiceRegistration

先来看看NacosAutoServiceRegistration的父类AbstractAutoServiceRegistration,注意AbstractAutoServiceRegistration是SpringCloud提供的类。

public abstract class AbstractAutoServiceRegistration<R extends Registration>
		implements AutoServiceRegistration, ApplicationContextAware, ApplicationListener<WebServerInitializedEvent> {

AbstractAutoServiceRegistration实现了ApplicationListener接口,并监听了WebServerInitializedEvent事件,在SpringBoot项目启动过程中会触发WebServerInitializedEvent事件,这样就会调用到onApplicationEvent()方法:

org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration#onApplicationEvent

public void onApplicationEvent(WebServerInitializedEvent event) {
	bind(event);
}

@Deprecated
public void bind(WebServerInitializedEvent event) {
	ApplicationContext context = event.getApplicationContext();
	if (context instanceof ConfigurableWebServerApplicationContext) {
		if ("management".equals(((ConfigurableWebServerApplicationContext) context).getServerNamespace())) {
			return;
		}
	}
	this.port.compareAndSet(0, event.getWebServer().getPort());
	this.start();
}

public void start() {
	if (!isEnabled()) {
		if (logger.isDebugEnabled()) {
			logger.debug("Discovery Lifecycle disabled. Not starting");
		}
		return;
	}

	// only initialize if nonSecurePort is greater than 0 and it isn't already running
	// because of containerPortInitializer below
	if (!this.running.get()) {
		this.context.publishEvent(new InstancePreRegisteredEvent(this, getRegistration()));
		register();
		if (shouldRegisterManagement()) { // false
			registerManagement();
		}
		this.context.publishEvent(new InstanceRegisteredEvent<>(this, getConfiguration()));
		this.running.compareAndSet(false, true);
	}

}

protected void register() {
	this.serviceRegistry.register(getRegistration());
}

最终会调用到register()方法,而NacosAutoServiceRegistration实现了register()方法:

com.alibaba.cloud.nacos.registry.NacosAutoServiceRegistration#register

protected void register() {
	if (!this.registration.getNacosDiscoveryProperties().isRegisterEnabled()) {
		log.debug("Registration disabled.");
		return;
	}
	if (this.registration.getPort() < 0) {
		this.registration.setPort(getPort().get());
	}
	super.register();
}

上面的方法也没干啥,又回调到父类AbstractAutoServiceRegistration的register()。

org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration#register

protected void register() {
	this.serviceRegistry.register(getRegistration());
}

最终会调用到serviceRegistry.register(),其中serviceRegistry是在NacosServiceRegistryAutoConfiguration中注入的NacosServiceRegistry。

NacosServiceRegistry

NacosServiceRegistry负责具体的服务注册。

com.alibaba.cloud.nacos.registry.NacosServiceRegistry#register

public void register(Registration registration) {

	if (StringUtils.isEmpty(registration.getServiceId())) {
		log.warn("No service to register for nacos client...");
		return;
	}

	// 获取NamingService
	NamingService namingService = namingService();
	String serviceId = registration.getServiceId();
	String group = nacosDiscoveryProperties.getGroup();

	// 将Registration封装为Instance
	Instance instance = getNacosInstanceFromRegistration(registration);

	try {
		// 注册实例
		namingService.registerInstance(serviceId, group, instance);
		log.info("nacos registry, {} {} {}:{} register finished", group, serviceId,
				 instance.getIp(), instance.getPort());
	}
	catch (Exception e) {
		if (nacosDiscoveryProperties.isFailFast()) {
			log.error("nacos registry, {} register failed...{},", serviceId,
					  registration.toString(), e);
			rethrowRuntimeException(e);
		}
		else {
			log.warn("Failfast is false. {} register failed...{},", serviceId,
					 registration.toString(), e);
		}
	}
}
NacosNamingService

第一次创建时会通过groupname + servicename创建一个心跳实例并启动心跳检测定时任务。

com.alibaba.nacos.client.naming.NacosNamingService#registerInstance(java.lang.String, java.lang.String, com.alibaba.nacos.api.naming.pojo.Instance)

public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
	NamingUtils.checkInstanceIsLegal(instance);
	// groupname 和 servicename 组合,DEFAULT_GROUP@@serviceName
    // 默认groupname为DEFAULT_GROUP
	String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
	if (instance.isEphemeral()) {
		// 构建心跳实例
		BeatInfo beatInfo = this.beatReactor.buildBeatInfo(groupedServiceName, instance);
		// 启动心跳定时任务
		this.beatReactor.addBeatInfo(groupedServiceName, beatInfo);
	}

	// 服务注册
	this.serverProxy.registerService(groupedServiceName, groupName, instance);
}
NamingProxy

发起Http调用,接口为/nacos/v1/ns/instance。

com.alibaba.nacos.client.naming.net.NamingProxy#registerService

public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
	LogUtils.NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}", new Object[]{this.namespaceId, serviceName, instance});
	Map<String, String> params = new HashMap(16);
	params.put("namespaceId", this.namespaceId);
	params.put("serviceName", serviceName);
	params.put("groupName", groupName);
	params.put("clusterName", instance.getClusterName());
	params.put("ip", instance.getIp());
	params.put("port", String.valueOf(instance.getPort()));
	params.put("weight", String.valueOf(instance.getWeight()));
	params.put("enable", String.valueOf(instance.isEnabled()));
	params.put("healthy", String.valueOf(instance.isHealthy()));
	params.put("ephemeral", String.valueOf(instance.isEphemeral()));
	params.put("metadata", JacksonUtils.toJson(instance.getMetadata()));
	this.reqApi(UtilAndComs.nacosUrlInstance, params, "POST");
}
BeatReactor

BeatReactor负责发送心跳包,默认每隔5s发送一次。

com.alibaba.nacos.client.naming.beat.BeatReactor#addBeatInfo

public void addBeatInfo(String serviceName, BeatInfo beatInfo) {
	LogUtils.NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);
	String key = this.buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort());
	BeatInfo existBeat = null;
	if ((existBeat = (BeatInfo)this.dom2Beat.remove(key)) != null) {
		existBeat.setStopped(true);
	}

	this.dom2Beat.put(key, beatInfo);
	this.executorService.schedule(new BeatReactor.BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);
	MetricsMonitor.getDom2BeatSizeMonitor().set((double)this.dom2Beat.size());
}