背景
公司使用nacos-discovery作為服務(wù)注冊(cè)和服務(wù)發(fā)現(xiàn),使用nacos-conf作為配置中心,通信的方式使用的是grpc,所以希望關(guān)閉http端口,使用grpc端口
技術(shù)方案
在application.properties文件中關(guān)閉web配置,即spring.main.web-application-type設(shè)置為 none,并且添加配置spring.cloud.nacos.discovery.port=XXX
一. 出現(xiàn)問(wèn)題
啟動(dòng)服務(wù)時(shí)一切正常,但是發(fā)現(xiàn)服務(wù)不往nacos上注冊(cè),也沒(méi)有出現(xiàn)任何異常提示,直接調(diào)用grpc端口可以正常通信。
二. nacos注冊(cè)原理和時(shí)機(jī)
nacos-client主要是通過(guò)com.alibaba.cloud.nacos.registry.NacosAutoServiceRegistration管理注冊(cè)行為,實(shí)際上就是向nacos-server上發(fā)送一個(gè)POST請(qǐng)求
該類繼承自org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration,這個(gè)類是spirng-cloud對(duì)于服務(wù)注冊(cè)的一個(gè)通用抽象,其中一個(gè)關(guān)鍵的接口是ApplicationListener<WebServerInitializedEvent>
換句話說(shuō),服務(wù)注冊(cè)到nacos上的時(shí)機(jī)是收到spirng中web組件成功實(shí)例化后發(fā)布的事件后。
那么這就合理了,將web端口關(guān)閉后,spring不會(huì)去初始化web相關(guān)實(shí)例,也就不會(huì)發(fā)布web實(shí)例初始化成功的事件,所以nacos不會(huì)執(zhí)行注冊(cè)的東西,而其他操作毫無(wú)影響
三. 解決方案
NacosServiceRegistry是nacos注冊(cè)到spring當(dāng)中的bean,其中的register()方法也是底層最終實(shí)現(xiàn)注冊(cè)動(dòng)作的類,所以我們可以通過(guò)將該bean注入到我們自己的服務(wù)中,并通過(guò)手動(dòng)調(diào)用的方式注冊(cè)到nacos-server上
四. 其他
另有同學(xué)說(shuō),有沒(méi)有可能grpc server正在實(shí)例化,端口就已經(jīng)注冊(cè)到nacos上了,會(huì)不會(huì)造成短暫的不可用?
spring啟動(dòng)的核心邏輯在org.springframework.context.support.AbstractApplicationContext#refresh方法中,如下
...
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
···
finishRefresh方法如下
protected void finishRefresh() {
// Clear context-level resource caches (such as ASM metadata from scanning).
clearResourceCaches();
// Initialize lifecycle processor for this context.
initLifecycleProcessor();
// Propagate refresh to lifecycle processor first.
getLifecycleProcessor().onRefresh();
// Publish the final event.
publishEvent(new ContextRefreshedEvent(this));
// Participate in LiveBeansView MBean, if active.
if (!IN_NATIVE_IMAGE) {
LiveBeansView.registerApplicationContext(this);
}
}
可以很清楚的看到,只有當(dāng)所有的bean都實(shí)例化完成之后才會(huì)輪到finishRefresh,而發(fā)布web事件是在getLifecycleProcessor().onRefresh();,所以只要grpc-server是一個(gè)spring的bean,那注冊(cè)到nacos上的端口就一定可用
結(jié)論
spring-cloud環(huán)境下,nacos的服務(wù)注冊(cè)是依賴于spring的事件發(fā)布的,我們自己的服務(wù)也可以利用事件發(fā)布在spring初始化后做一些相應(yīng)的操作