Nacos客戶端入口
首先在我們使用Nacos時,會在客戶端引入對應的依賴,例如需要Nacos的注冊中心功能需要引入
<!--nacos-discovery 注冊中心依賴--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency>
而熟悉Springboot的自然知道,為什么引用一個starter就能引入對應的功能?這離不開Springboot的自動配置功能,這也是其特點之一。
而說到自動配置,那自然離不開自動配置類,他即是功能的入口。
那Nacos有哪些自動配置類呢?
NacosServiceAutoConfiguration
這是Nacos服務器通信的基礎設施,是整個 Nacos 集成的"連接管理中心"。
@Configuration(proxyBeanMethods = false
)
@ConditionalOnDiscoveryEnabled
@ConditionalOnNacosDiscoveryEnabled
public class NacosServiceAutoConfiguration {public NacosServiceAutoConfiguration() {}@Beanpublic NacosServiceManager nacosServiceManager() {return new NacosServiceManager();}// Spring 應用 → NacosServiceManager → Nacos SDK → Nacos 服務器}
NacosServiceManager 被多個 Nacos 相關組件使用:
服務發現組件:
NacosServiceDiscovery 使用它獲取 NamingService 實例
NacosRegistration 使用它進行服務注冊
配置中心組件:
NacosConfigManager 使用它獲取 ConfigService 實例
配置刷新機制依賴它提供的服務對象
延遲初始化: NacosServiceManager 不會在創建時立即連接 Nacos 服務器,而是采用延遲初始化策略
按需創建服務對象:
當系統需要 NamingService (服務發現) 時,按需創建并緩存
當系統需要 ConfigService (配置中心) 時,按需創建并緩存
服務對象緩存機制:
基于屬性(如命名空間、分組)創建緩存鍵
相同配置的服務請求復用同一個服務對象實例
避免重復創建連接,優化資源使用
生命周期管理:
與 Spring 容器生命周期綁定
在應用關閉時釋放資源、關閉連接
NacosDiscoveryAutoConfiguration
這是Nacos的服務發現自動配置類,主要有如下功能
- 自動裝配 Nacos 服務發現組件
- 初始化服務發現相關的基礎設施 Bean
- 使應用能夠與 Nacos 服務器進行服務注冊與發現交互
@Configuration(proxyBeanMethods = false
)
@ConditionalOnDiscoveryEnabled
@ConditionalOnNacosDiscoveryEnabled
public class NacosDiscoveryAutoConfiguration {public NacosDiscoveryAutoConfiguration() {}@Bean@ConditionalOnMissingBeanpublic NacosDiscoveryProperties nacosProperties() {return new NacosDiscoveryProperties();}@Bean@ConditionalOnMissingBean // 需要傳入NacosServiceManager ,通過其進行服務發現public NacosServiceDiscovery nacosServiceDiscovery(NacosDiscoveryProperties discoveryProperties, NacosServiceManager nacosServiceManager) { return new NacosServiceDiscovery(discoveryProperties, nacosServiceManager);}
}
NacosServiceRegistryAutoConfiguration
上面兩個,一個是提供操作服務的Service,一個是提供服務發現的功能,那這個就是提供自動注冊的功能,其自動配置類如下:
@Configuration(proxyBeanMethods = false
)
@EnableConfigurationProperties
@ConditionalOnNacosDiscoveryEnabled // 自定義Condition注解,具體參考自動配置相關知識
@ConditionalOnProperty(value = {"spring.cloud.service-registry.auto-registration.enabled"},matchIfMissing = true
)
// 用于控制自動配置順序
@AutoConfigureAfter({AutoServiceRegistrationConfiguration.class, AutoServiceRegistrationAutoConfiguration.class, NacosDiscoveryAutoConfiguration.class})
public class NacosServiceRegistryAutoConfiguration {public NacosServiceRegistryAutoConfiguration() {}@Bean // 用于提供注冊功能的Service,其調用NacosServiceManager 進行實現 父類為 ServiceRegistry<R>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((List)registrationCustomizers.getIfAvailable(), nacosDiscoveryProperties, context);}@Bean // 自動服務注冊的觸發器,監聽事件并執行注冊@ConditionalOnBean({AutoServiceRegistrationProperties.class})public NacosAutoServiceRegistration nacosAutoServiceRegistration(NacosServiceRegistry registry, AutoServiceRegistrationProperties autoServiceRegistrationProperties, NacosRegistration registration) {return new NacosAutoServiceRegistration(registry, autoServiceRegistrationProperties, registration);}
}
自動注冊剖析
在上述第三個自動配置生效后,會返回一個 new NacosAutoServiceRegistration(registry, autoServiceRegistrationProperties, registration);
分析其繼承關系。
可以發現其關系圖中,有 ApplicationListener< WebServerInitializedEvent > 這個類,其作用是會監聽對應事件,而這個事件就是WebServerInitializedEvent 代表嵌入式 Web 服務器已完成初始化并啟動的通知機制。
public void onApplicationEvent(WebServerInitializedEvent event) {ApplicationContext context = event.getApplicationContext();if (!(context instanceof ConfigurableWebServerApplicationContext) || !"management".equals(((ConfigurableWebServerApplicationContext)context).getServerNamespace())) {this.port.compareAndSet(0, event.getWebServer().getPort());this.start(); // start方法}}
==》
start 方法
public void start() {if (!this.isEnabled()) {if (logger.isDebugEnabled()) {logger.debug("Discovery Lifecycle disabled. Not starting");}} else {if (!this.running.get()) {this.context.publishEvent(new InstancePreRegisteredEvent(this, this.getRegistration()));this.registrationLifecycles.forEach((registrationLifecycle) -> {registrationLifecycle.postProcessBeforeStartRegister(this.getRegistration());});this.register(); // 進行注冊this.registrationLifecycles.forEach((registrationLifecycle) -> {registrationLifecycle.postProcessAfterStartRegister(this.getRegistration());});if (this.shouldRegisterManagement()) {this.registrationManagementLifecycles.forEach((registrationManagementLifecycle) -> {registrationManagementLifecycle.postProcessBeforeStartRegisterManagement(this.getManagementRegistration());});this.registerManagement();this.registrationManagementLifecycles.forEach((registrationManagementLifecycle) -> {registrationManagementLifecycle.postProcessAfterStartRegisterManagement(this.getManagementRegistration());});}this.context.publishEvent(new InstanceRegisteredEvent(this, this.getConfiguration()));this.running.compareAndSet(false, true);}}}
register方法
private final ServiceRegistry<R> serviceRegistry;
......
protected void register() {if (!this.registration.getNacosDiscoveryProperties().isRegisterEnabled()) {log.debug("Registration disabled.");} else {if (this.registration.getPort() < 0) {this.registration.setPort(this.getPort().get());}super.register();}}
// ==> super.register()
protected void register() {this.serviceRegistry.register(this.getRegistration()); // NacosRegistration}
serviceRegistry.register
public void register(Registration registration) {if (StringUtils.isEmpty(registration.getServiceId())) {log.warn("No service to register for nacos client...");} else {NamingService namingService = this.namingService();String serviceId = registration.getServiceId();String group = this.nacosDiscoveryProperties.getGroup();Instance instance = this.getNacosInstanceFromRegistration(registration);try {namingService.registerInstance(serviceId, group, instance); // 進行注冊log.info("nacos registry, {} {} {}:{} register finished", new Object[]{group, serviceId, instance.getIp(), instance.getPort()});} catch (Exception var7) {Exception e = var7;if (this.nacosDiscoveryProperties.isFailFast()) {log.error("nacos registry, {} register failed...{},", new Object[]{serviceId, registration.toString(), e});ReflectionUtils.rethrowRuntimeException(e);} else {log.warn("Failfast is false. {} register failed...{},", new Object[]{serviceId, registration.toString(), e});}}}}
namingService.registerInstance
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {NamingUtils.checkInstanceIsLegal(instance);this.checkAndStripGroupNamePrefix(instance, groupName);this.clientProxy.registerService(serviceName, groupName, instance); // 通過代理去獲取對應的注冊服務}
clientProxy.registerService
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {this.getExecuteClientProxy(instance).registerService(serviceName, groupName, instance);}
getExecuteClientProxy
private NamingClientProxy getExecuteClientProxy(Instance instance) {return (NamingClientProxy)(!instance.isEphemeral() && !this.grpcClientProxy.isAbilitySupportedByServer(AbilityKey.SERVER_SUPPORT_PERSISTENT_INSTANCE_BY_GRPC) ? this.httpClientProxy : this.grpcClientProxy);}
是否臨時服務判斷
Nacos 提供兩種注冊方式,一種是臨時,會進行心跳檢測,如果心跳檢測不超過,或者超時,就會被任務不健康實例,會對其進行下線,其次會將其加入一個隊列中,去檢測是否健康,如果不健康,那就移除。
一種是永久注冊,下面詳細介紹
臨時實例 (Ephemeral = true)
-
生命周期特點:
- 基于心跳維持,客戶端需要定期發送心跳包
- 如果一定時間內未收到心跳,Nacos會自動將實例標記為不健康,并最終移除
-
適用場景:
- 適合大多數微服務場景,特別是云原生環境
- 實例會隨應用的啟停自動注冊和注銷
-
數據存儲:
- 存儲在內存中,提供更高的性能
- 在Nacos服務器重啟時數據會丟失
持久化實例 (Ephemeral = false)
-
生命周期特點:
- 實例信息持久化到磁盤
- 不依賴心跳維持,即使客戶端宕機實例也不會被自動移除
-
適用場景:
- 適合那些需要保持穩定注冊狀態的場景
- 如某些關鍵基礎設施服務,需要手動管理其上下線
-
數據存儲:
- 數據持久化到磁盤數據庫中
- Nacos重啟后數據仍然存在
HttpClient.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});String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);if (instance.isEphemeral()) {throw new UnsupportedOperationException("Do not support register ephemeral instances by HTTP, please use gRPC replaced.");} else {Map<String, String> params = new HashMap(32);params.put("namespaceId", this.namespaceId);params.put("serviceName", groupedServiceName);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");}}
Grpc.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});if (instance.isEphemeral()) {this.registerServiceForEphemeral(serviceName, groupName, instance);} else {this.doRegisterServiceForPersistent(serviceName, groupName, instance);}}
自上,自動注冊服務就到此為止