【七夕快乐篇】Nacos是如何实现服务注册功能的?

《Java系核心技术》 《中间件核心技术》
《微服务核心技术》 《云原生核心技术》

今天是一个美好的日子,祝大家七夕快乐。

很多订阅 《微服务核心技术》 专栏的读者在后台私信说:看Nacos源码时没有思路,面试中还总被问到一些细节。

那么接下的几天里,我们就来逐步分析一下Nacos的源码以及Nacos的核心功能与机制,并着手写一个注册中心,来帮助大家更好的了解分布式中间件。

大家都知道Nacos有两大模块:注册中心和配置中心。

那么Nacos是如何实现注册中心的服务注册的功能呢?我们来一探究竟。

在SpringBoot的基底下,每当我们引入一个新的适配组件,理应看一下该组件下的/META-INF/spring.factories文件,上一篇文章《注解@EnableAutoConfiguration的作用以及如何使用》提到,@SpringBootApplication会自动加载/META-INF/spring.factories文件。

【七夕快乐篇】Nacos是如何实现服务注册功能的?_第1张图片
跟进NacosServiceRegistryAutoConfiguration,这个类主要是完成服务注册功能等。

【七夕快乐篇】Nacos是如何实现服务注册功能的?_第2张图片

@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
	@Bean
	@ConditionalOnBean(AutoServiceRegistrationProperties.class)
	public NacosAutoServiceRegistration nacosAutoServiceRegistration(
			NacosServiceRegistry registry,
			AutoServiceRegistrationProperties autoServiceRegistrationProperties,
			NacosRegistration registration) {
		return new NacosAutoServiceRegistration(registry,
				autoServiceRegistrationProperties, registration);
	}

}

跟进NacosAutoServiceRegistration

	public NacosAutoServiceRegistration(ServiceRegistry<Registration> serviceRegistry,
			AutoServiceRegistrationProperties autoServiceRegistrationProperties,
			NacosRegistration registration) {
		super(serviceRegistry, autoServiceRegistrationProperties);
		this.registration = registration;
	}

跟进super【AbstractAutoServiceRegistration

	protected AbstractAutoServiceRegistration(ServiceRegistry<R> serviceRegistry,
			AutoServiceRegistrationProperties properties) {
		this.serviceRegistry = serviceRegistry;
		this.properties = properties;
	}

在这个类下,有个监听的方法,这个就是服务注册的核心方法了。

	@Override
	@SuppressWarnings("deprecation")
	public void onApplicationEvent(WebServerInitializedEvent event) {
		// 服务注册的核心方法
		bind(event);
	}

那么有监听的事件,就应该有发布的事件,那么事件是在哪里发布的呢?

事件是在WebServerStartStopLifecycle#start时发布的

spring容器启动过程中核心方法:finishRefresh(),让我们看一看这个方法,算了,我还是专门写一篇吧…移步《Spring源码之finishRefresh()》

SpringApplication#run启动过程中核心方法:

  1. finishRefresh()

  2. getLifecycleProcessor().onRefresh();

WebServerStartStopLifecycle(实现SmartLifecycle接口)会发布 ServletWebServerInitializedEvent事件。

NacosAutoServiceRegistrationonApplicationEvent方法处理WebServerInitializedEvent事件。

	@Override
	public void start() {
		this.webServer.start();
		this.running = true;
		this.applicationContext
				.publishEvent(new ServletWebServerInitializedEvent(this.webServer, this.applicationContext));
	}

知道了它是如何发布的,我们来看一下AbstractAutoServiceRegistration#bind

	@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();
	}

跟进this.start()

【七夕快乐篇】Nacos是如何实现服务注册功能的?_第3张图片
跟进register(),几经辗转,我们来到NacosServiceRegistry#register

【七夕快乐篇】Nacos是如何实现服务注册功能的?_第4张图片

跟进namingService.registerInstance(serviceId, group, instance);,我们来到NacosNamingService#registerInstance,我们重点关注一下

【七夕快乐篇】Nacos是如何实现服务注册功能的?_第5张图片
跟进NamingProxy#registerService,组装客户端信息向服务端发送请求。
【七夕快乐篇】Nacos是如何实现服务注册功能的?_第6张图片
由此可见,服务注册的请求是POST,请求路径是/nacos/v1/ns/instance

    public static String webContext = "/nacos";
    
    public static String nacosUrlBase = webContext + "/v1/ns";
    
    public static String nacosUrlInstance = nacosUrlBase + "/instance";

根据上述结论,我们可以找到服务端对应的API接口:InstanceController#register
【七夕快乐篇】Nacos是如何实现服务注册功能的?_第7张图片

    @CanDistro
    @PostMapping
    @Secured(action = ActionTypes.WRITE)
    public String register(HttpServletRequest request) throws Exception {
        
        final String namespaceId = WebUtils
                .optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
        final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
        NamingUtils.checkServiceNameFormat(serviceName);
        
        final Instance instance = HttpRequestInstanceBuilder.newBuilder()
                .setDefaultInstanceEphemeral(switchDomain.isDefaultInstanceEphemeral()).setRequest(request).build();
        // 跟进
        getInstanceOperator().registerInstance(namespaceId, serviceName, instance);
        NotifyCenter.publishEvent(new RegisterInstanceTraceEvent(System.currentTimeMillis(), "",
                false, namespaceId, NamingUtils.getGroupName(serviceName), NamingUtils.getServiceName(serviceName),
                instance.getIp(), instance.getPort()));
        return "ok";
    }
    

跟进getInstanceOperator().registerInstance(namespaceId, serviceName, instance);

InstanceOperatorServiceImpl#registerInstance

    @Override
    public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
        com.alibaba.nacos.naming.core.Instance coreInstance = parseInstance(instance);
        // 跟进
        serviceManager.registerInstance(namespaceId, serviceName, coreInstance);
    }

跟进serviceManager.registerInstance(namespaceId, serviceName, coreInstance);

ServiceManager#registerInstance

    public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
        
        NamingUtils.checkInstanceIsLegal(instance);
        // 跟进
        createEmptyService(namespaceId, serviceName, instance.isEphemeral());
        
        Service service = getService(namespaceId, serviceName);
        
        checkServiceIsNull(service, namespaceId, serviceName);
        
        addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
    }
    

跟进createEmptyService(namespaceId, serviceName, instance.isEphemeral());

最后我们来到ServiceManager#createServiceIfAbsent

 public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster)
            throws NacosException {
        Service service = getService(namespaceId, serviceName);
        //return if service already exists
        if (service != null) {
            return;
        }

        Loggers.SRV_LOG.info("creating empty service {}:{}", namespaceId, serviceName);
        service = new Service();
        service.setName(serviceName);
        service.setNamespaceId(namespaceId);
        service.setGroupName(NamingUtils.getGroupName(serviceName));
        // now validate the service. if failed, exception will be thrown
        service.setLastModifiedMillis(System.currentTimeMillis());
        service.recalculateChecksum();
        if (cluster != null) {
            cluster.setService(service);
            service.getClusterMap().put(cluster.getName(), cluster);
        }
        service.validate();
		
		// 跟进
        putServiceAndInit(service);
        if (!local) {
            addOrReplaceService(service);
        }
    }

跟进putServiceAndInit(service);

private void putServiceAndInit(Service service) throws NacosException {
		// 跟进
        putService(service);
        service = getService(service.getNamespaceId(), service.getName());
        service.init();
        consistencyService
                .listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), true), service);
        consistencyService
                .listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), false), service);
        Loggers.SRV_LOG.info("[NEW-SERVICE] {}", service.toJson());
    }

跟进putService(service);

    public void putService(Service service) {
        if (!serviceMap.containsKey(service.getNamespaceId())) {
       		// 将客户端信息存入服务端内存中
            serviceMap.putIfAbsent(service.getNamespaceId(), new ConcurrentSkipListMap<>());
        }
        serviceMap.get(service.getNamespaceId()).putIfAbsent(service.getName(), service);
    }
    

再看一眼这个serviceMap的定义

/**
 * Map(namespace, Map(group::serviceName, Service)).
 */
private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();

来吧,总结一下吧,大体分为这么几步:

  1. Spring启动时,先发布Nacos服务注册的事件
  2. 实例化Nacos服务注册的核心类NacosAutoServiceRegistration,并监听事件
  3. 监听到事件之后,对事件进行处理,封装Nacos客户端信息,并发送API请求到Nacos服务端接口
  4. 服务端接收到请求之后,将数据存到初始化的ConcurrentHashMap中,一次完成服务注册

接下来,我们逐步来分析一下Nacos的其他核心机制,并手写一个注册中心,让大家更好的了解这些分布式中间件。

你可能感兴趣的:(微服务核心技术,云原生,微服务)