Nacos如何實現服務自動注冊


1. 背景

接着上次的說,Nacos問題的分析。這次就來分享一下自己的分析方法。以及如何利用Spring事件完成服務注冊。

2. 分析一下

2.1 問題分析過程

問題是這樣的,我以為是Nacos服務注冊不上嗎? 那就肯定是注冊有問題了, 那就去找是服務是如何注冊的。

關鍵是從哪找呢?具體步驟:

  1. 知道服務啟動方式。如Nacos,就是通過properties,那就找哪有用到啟動的屬性文件
  2. 項目用SpringBoot集成,那核心類的加載就在Spring.factories中。(這個文件會被springSPI加載到)
  3. 找到關鍵類

很湊巧,我們在spring.factories中找到了名字很像啟動注冊類的類。如下圖:
Spring.factories

在找類的時候不要怕錯,大膽猜,只要驗證了自己的想法即可,不對就在接着找。

com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration類我們找到了,接下來就是去看這個類中的代碼去驗證自己的想法了。

2.2 Nacos是如何利用Spring事件來實現服務注冊的?

com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration代碼如下:

public class NacosServiceRegistryAutoConfiguration {
	// 注冊類實現,驗證我們的想法,需要用到NacosDiscoverProperties
	@Bean
	public NacosServiceRegistry nacosServiceRegistry(
			NacosDiscoveryProperties nacosDiscoveryProperties) {
		return new NacosServiceRegistry(nacosDiscoveryProperties);
	}
	// 構建注冊事例,依然要用到NacosDiscoverProperties
	@Bean
	@ConditionalOnBean(AutoServiceRegistrationProperties.class)
	public NacosRegistration nacosRegistration(
			NacosDiscoveryProperties nacosDiscoveryProperties,
			ApplicationContext context) {
		return new NacosRegistration(nacosDiscoveryProperties, context);
	}
	// 服務自動注冊,通過上面實例化的兩個類
	@Bean
	@ConditionalOnBean(AutoServiceRegistrationProperties.class)
	public NacosAutoServiceRegistration nacosAutoServiceRegistration(
			NacosServiceRegistry registry,
			AutoServiceRegistrationProperties autoServiceRegistrationProperties,
			NacosRegistration registration) {
		return new NacosAutoServiceRegistration(registry,
				autoServiceRegistrationProperties, registration);
	}
}

通過查看代碼,發現和我們猜想沒有出入,是通過NacosDiscoverProperties來進行初始化,和官方給的Demo也相似(只是沒繼承SpringBoot,名字不叫這個),前面兩個類我們先不用關注(好奇的可以先點進去看看),我們直接進入第三個方法NacosAutoServiceRegistration中,通過名字我們也可以看出,這個是自動注冊的,參數中包含了Registry和Registration。我們進入類中接着查看。

public class NacosAutoServiceRegistration
		extends AbstractAutoServiceRegistration<Registration> {

	private NacosRegistration registration;
	// 構造參數,關鍵
	public NacosAutoServiceRegistration(ServiceRegistry<Registration> serviceRegistry,
			AutoServiceRegistrationProperties autoServiceRegistrationProperties,
			NacosRegistration registration) {
		super(serviceRegistry, autoServiceRegistrationProperties);
		this.registration = registration;
	}
        // 注冊方法
        @Override
	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();
	}
}  

進入類中,我們發現了register()方法,更進一步驗證我們想法,是通過這個類來進行服務注冊,但是在register()方法中,調用了父類的register()方法, 這點就要引起我們的好奇,父類都沒都啥東西,調用父類的方法有啥用呢? 這個時候就想到了肯定有構造參數,或者方法初始化父類的東西啦!

我們觀察構造參數,發現把serviceRegistry傳入到了父類構造參數中,我們直接查看父類代碼。

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

AbstractAutoServiceRegistration是繼承了ApplicationListener<WebServerInitializedEvent>,看到這,不就是我們熟悉的領域了嗎?繼承了ApplicationListener那就不多BB,直接看onApplicationEvent()方法就好啦。(這個事件是WebServerInitializedEvent,這點還是要知道的哈)

	@Override
	@SuppressWarnings("deprecation")
	public void onApplicationEvent(WebServerInitializedEvent event) {
                // 調用了bind方法
		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());
                // 調用了start()方法
		this.start();
	}

我們直接進入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();
                        // 服務注冊事件
			this.context.publishEvent(
					new InstanceRegisteredEvent<>(this, getConfiguration()));
			this.running.compareAndSet(false, true);
		}

	}

start()方法里面還發布了兩個事件,可見Spring中,服務的注冊前后都有一些監聽器來處理服務信息。先跳過這些,我們直接關注我們的核心——注冊,直接進入register()方法中。

	protected void register() {
                // this.serviceRegistry 是我們傳遞進來的,
                // getRegistration() 是在NacosAutoServiceRegistration中實現的
		this.serviceRegistry.register(getRegistration());
	}

到這我們基本知道是怎么回事了,this.serviceRegistry是傳遞進來的,那調用的register()方法則是Nacos自己實現的注冊,getRegistration()NacosServiceRegistryAutoConfiguration中通過nacosDiscoveryProperties生成的。那我們就看一下register()方法就好啦。

	@Override
	public void register(Registration registration) {

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

		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) {
			log.error("nacos registry, {} register failed...{},", serviceId,
					registration.toString(), e);
			// rethrow a RuntimeException if the registration is failed.
			// issue : https://github.com/alibaba/spring-cloud-alibaba/issues/1132
			rethrowRuntimeException(e);
		}
	}

到這我們就看懂了,也知道大概流程了。流程圖如下

3. 吸星大法:吸收代碼經驗

Spring 在實現的時候,通過設計模式(裝飾),實現流程,又把關鍵部分丟給開發自己實現,提高了拓展性,然后巧妙的結合了觀察者模式(變種),在合適的時間注冊服務,妙啊~~

Nacos在實現的時候,深知Spring各種屬性,各種用法,一定是對Spring有個深入了解的人(或者正好看過? 這個解釋有點牽強),結合AutoRegistry和事件(不知道,肯定不知道怎么調用的),妙啊~~

看到這實現的代碼,簡直舒服,妙啊~~

xua~~~~(), 爽啊 ~~

4. 總結

總的看下來,這個啟動,只能有Web容器的時候才能注冊, 我猜主要是和namingServer注冊有關,所以也不能算是一個bug了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM