Nacos 的工作流程剖析

image-20250719124926528

Nacos 服务注册部分

有这么一个包,spring-cloud-commons

image-20250719124212246

在这里,你会看到一个熟悉的包,loadbalancer,这就是我们之前深度分析的负载均衡的包,Nacos的负载均衡先不讲,在这个包下面,有个 serviceregistry包,这个包的作用就是服务注册相关内容的包

image-20250719125222356

其中,ServiceRegistry 包是关注与服务注册相关的核心接口,这个ServiceRegistry接口是Spring Cloud提供的服务注册的标准,集成到SpringCloud中实现服务注册的组件,都需要实现这个接口。看一下它的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package org.springframework.cloud.client.serviceregistry;

/**
* 服务注册中心接口,定义了服务注册、注销、关闭、设置状态和获取状态等操作,
* 为不同的服务注册中心(如Nacos、Eureka等)提供统一的服务注册与管理规范。
* @param <R> 服务注册信息的类型,需继承自Registration接口
*/
public interface ServiceRegistry<R extends Registration> {
/**
* 将服务实例注册到服务注册中心
*
* @param registration 服务注册信息,包含服务实例的相关信息,如服务名、IP、端口等
*/
void register(R registration);

/**
* 从服务注册中心注销服务实例
*
* @param registration 要注销的服务实例的注册信息
*/
void deregister(R registration);

/**
* 关闭服务注册中心的客户端连接或资源
*/
void close();

/**
* 设置服务实例的状态
*
* @param registration 服务实例的注册信息
* @param status 要设置的状态
*/
void setStatus(R registration, String status);

/**
* 获取服务实例的状态
*
* @param registration 服务实例的注册信息
* @param <T> 状态的类型
* @return 服务实例的状态
*/
<T> T getStatus(R registration);
}

那么,Erueka有着它对应的实现类,Nacos也有,对于Nacos而言,该接口的实现类是NacosServiceRegistry,它在com.alibaba.cloud.nacos.registry;包下,这个包就是 Nacos 的服务注册的具体实现

image-20250719135343514

其中的成员变量,nacosDiscoveryPropertiesNacosDiscoveryProperties类型,用于获取 Nacos 服务发现相关的配置信息,而nacosServiceManagerNacosServiceManager类型,负责管理 Nacos 的命名服务(NamingService),包括获取、创建和关闭等操作。

核心方法register(Registration registration)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public void register(Registration registration) {
// 首先检查服务 ID 是否为空,如果为空则记录警告日志并返回。
if (StringUtils.isEmpty(registration.getServiceId())) {
log.warn("No service to register for nacos client...");
} else {
// 从nacosServiceManager中获取NamingService实例,用于与 Nacos 服务端进行交互。
NamingService namingService = this.namingService();
String serviceId = registration.getServiceId();
String group = this.nacosDiscoveryProperties.getGroup();
// 构建Instance实例
Instance instance = this.getNacosInstanceFromRegistration(registration);

try {
// 调用NamingService的registerInstance方法将服务实例注册到 Nacos 服务端。
namingService.registerInstance(serviceId, group, instance);
log.info("nacos registry, {} {} {}:{} register finished", new Object[]{group, serviceId, instance.getIp(), instance.getPort()});
} catch (Exception e) {
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});
}
}

}
}

而挑出来这个setStatus(Registration registration, String status)讲一下,这是 Nacos 设置服务状态的核心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void setStatus(Registration registration, String status) {
// 设置服务实例的状态(如 “UP” 表示正常,“DOWN” 表示不可用)。
if (!"UP".equalsIgnoreCase(status) && !"DOWN".equalsIgnoreCase(status)) {
log.warn("can't support status {},please choose UP or DOWN", status);
} else {
// 获取服务 ID 和构建Instance实例。
String serviceId = registration.getServiceId();
Instance instance = this.getNacosInstanceFromRegistration(registration);
// 根据状态设置Instance的enabled属性(“UP” 则启用,“DOWN” 则禁用)。
if ("DOWN".equalsIgnoreCase(status)) {
instance.setEnabled(false);
} else {
instance.setEnabled(true);
}

try {
// 调用nacosServiceManager的getNamingMaintainService方法获取NamingMaintainService实例,并调用其updateInstance方法更新服务实例的状态到 Nacos 服务端。
Properties nacosProperties = this.nacosDiscoveryProperties.getNacosProperties();
this.nacosServiceManager.getNamingMaintainService(nacosProperties).updateInstance(serviceId, this.nacosDiscoveryProperties.getGroup(), instance);
} catch (Exception e) {
throw new RuntimeException("update nacos instance status fail", e);
}
}
}

AutoServiceRegistrationAutoConfiguration这个类,顾名思义是服务注册相关的配置类。这个配置类是 Spring Cloud 服务注册功能的核心配置,它实现了服务的自动注册和注销功能,使应用能够自动向服务注册中心注册自己,并在应用关闭时自动注销。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
/**
* 自动服务注册自动配置类
* 该配置类负责在Spring Cloud应用中自动注册服务到服务注册中心
*/
@Configuration(proxyBeanMethods = false)
// 导入AutoServiceRegistrationConfiguration配置类
@Import({AutoServiceRegistrationConfiguration.class})
@ConditionalOnProperty(
value = {"spring.cloud.service-registry.auto-registration.enabled"},
matchIfMissing = true
)
// 启用AutoServiceRegistrationProperties配置属性,支持通过配置文件自定义服务注册行为
@EnableConfigurationProperties({AutoServiceRegistrationProperties.class})
public class AutoServiceRegistrationAutoConfiguration implements InitializingBean {

// 自动服务注册接口实现类
@Autowired(required = false)
private AutoServiceRegistration autoServiceRegistration;

// 自动服务注册属性配置
@Autowired
private AutoServiceRegistrationProperties properties;

/**
* 初始化方法,在bean初始化完成后执行
* 检查是否配置了AutoServiceRegistration bean,当配置为failFast时如果未配置则抛出异常
*/
@Override
public void afterPropertiesSet() {
if (this.autoServiceRegistration == null && this.properties.isFailFast()) {
throw new IllegalStateException("Auto Service Registration has been requested, but there is no AutoServiceRegistration bean");
}
}

/**
* 创建自动服务注册实例
*
* @param serviceRegistry 服务注册中心接口
* @param registration 服务注册信息
* @param autoServiceRegistrationProperties 自动服务注册属性
* @param inetUtils 网络工具类
* @return 自动服务注册实例
*/
@Bean
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
public AutoServiceRegistration autoServiceRegistration(
ServiceRegistry<?> serviceRegistry,
Registration registration,
AutoServiceRegistrationProperties autoServiceRegistrationProperties,
InetUtils inetUtils) {
return new AutoServiceRegistrationAdapter(serviceRegistry, registration,
autoServiceRegistrationProperties, inetUtils);
}

/**
* 自动服务注册适配器实现类,创建AutoServiceRegistrationAdapter内部类,实现AutoServiceRegistration接口
*/
private static class AutoServiceRegistrationAdapter implements AutoServiceRegistration {

private final ServiceRegistry<?> serviceRegistry;
private final Registration registration;
private final AutoServiceRegistrationProperties properties;
private final InetUtils inetUtils;

public AutoServiceRegistrationAdapter(ServiceRegistry<?> serviceRegistry,
Registration registration,
AutoServiceRegistrationProperties properties, InetUtils inetUtils) {
this.serviceRegistry = serviceRegistry;
this.registration = registration;
this.properties = properties;
this.inetUtils = inetUtils;
}

/**
* 注册服务实例到服务注册中心
*/
@Override
public void start() {
if (!this.properties.isEnabled()) {
return;
}

// 注册服务实例
this.serviceRegistry.register(this.registration);

// 记录注册日志
if (log.isInfoEnabled()) {
log.info("Auto service registration enabled. Registering service with name: {}",
this.registration.getServiceId());
}
}

/**
* 从服务注册中心注销服务实例,支持通过配置文件自定义服务注册行为
*/
@Override
public void stop() {
if (!this.properties.isEnabled()) {
return;
}

// 注销服务实例
this.serviceRegistry.deregister(this.registration);

// 记录注销日志
if (log.isInfoEnabled()) {
log.info("Auto service registration disabled. Deregistering service with name: {}",
this.registration.getServiceId());
}
}
}

private static final Logger log = LoggerFactory.getLogger(AutoServiceRegistrationAutoConfiguration.class);
}

NacosAutoServiceRegistration继承了这个类,而NacosAutoServiceRegistration这个的继承实现关系如下

image-20250719140057343

再来看看看这个抽象类,AbstractAutoServiceRegistration,这个类长的一批,但是它实现了以下接口:

1
public abstract class AbstractAutoServiceRegistration<R extends Registration> implements AutoServiceRegistration, ApplicationContextAware, ApplicationListener<WebServerInitializedEvent> {
  • AutoServiceRegistration:服务自动注册接口
  • ApplicationContextAware:用于获取 Spring 应用上下文
  • ApplicationListener<WebServerInitializedEvent>:监听 Web 服务器初始化事件

R extends Registration 表示参数必须是Registration接口的实现类,这使得该类可以处理不同类型的服务注册信息,所以说,这个类是负责服务的自动注册功能。

NacosAutoServiceRegistration监听WebServerInitializedEvent事件。而在也就是WebServer初始化完成后,调用onApplicationEvent(),也就是事件被监听到,然后从事件中获取上下文对象,检查上下文是否是ConfigurableWebServerApplicationContext类型,之后调用start()方法开始服务注册流程

再继续作为抽象类的子类实现NacosAutoServiceRegistration,监听到Web服务启动后, 开始执行super.register()方法。

1
2
3
4
5
6
7
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();
}
}
image-20250719141045941

继续,来看看register()方法,对于register()方法,主要调用的是Nacos Client SDK中的NamingService下的registerInstance()方法完成服务的注册

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

可以看到,AbstractAutoServiceRegistration抽象类的方法调度的是serviceRegistry的服务注册标准组件,而也就是实际上调度的是NacosServiceRegistry,这个方法最终会调用 NacosServiceRegistryregister()方法(别忘了NacosServiceRegistry实现了Spring的一个服务注册标准接口)。在上面,我们仔细分析了这个方法。

对于register()方法,核心主要调用的是Nacos Client SDK中的NamingService下的registerInstance()方法完成服务的注册

1
2
3
4
try {
// 调用NamingService的registerInstance方法将服务实例注册到 Nacos 服务端。
namingService.registerInstance(serviceId, group, instance);
log.info("nacos registry, {} {} {}:{} register finished", new Object[]{group, serviceId, instance.getIp(), instance.getPort()});

可以看到,其中三个registerInstance()方法都是对配置规则和默认配置的选择执行

image-20250719142312477

NamingService 这个接口在前面我们说过了,它定义了与服务注册和发现相关的核心操作,是 Nacos 客户端进行服务注册与发现功能的关键抽象。

也就是说,AbstractAutoServiceRegistration这个抽象类是 Spring Cloud 服务自动注册的核心抽象类,而在 Nacos 服务注册流程中扮演调度中心的角色。它承上启下,连接 Spring Boot 的启动生命周期与 Nacos 的服务注册逻辑,NacosAutoServiceRegistration继承此类,实现抽象方法(如getRegistration()返回 Nacos 的服务实例对象)。而且通过ServiceRegistry接口依赖NacosServiceRegistry,将注册逻辑委托给 Nacos 客户端。

Nacos心跳检测机制

继续我们的过程,在上述AbstractAutoServiceRegistration抽象方法进行反复调用,调用到NacosServiceRegistry下的 register方法中,我们继续深入,其实还会发现这个创建实例的方法

1
Instance instance = this.getNacosInstanceFromRegistration(registration);

这个方法其实也不难,就是 set 各种内容,完成实例的创建,但是其中有这么一条

1
instance.setEphemeral(this.nacosDiscoveryProperties.isEphemeral());

这个方法返回的是该实例是否是临时实例。还记得之前所说的心跳机制吗,临时实例会使用心跳机制,而非临时实例则不会。我们继续找,发现这个配置类nacosDiscoveryProperties

image-20250719184454100

这个配置类记录了 Nacos 大部分服务发现的详细配置内容,且标注了@ConfigurationProperties("spring.cloud.nacos.discovery")注解,意味着都可以在配置文件中进行详细的配置

NacosDiscoveryProperties 类中,有几个与心跳机制相关的属性:

image-20250719184752499
  • heartBeatInterval:心跳间隔,即客户端向 Nacos 服务器发送心跳的时间间隔。
  • heartBeatTimeout:心跳超时时间,即 Nacos 服务器等待客户端心跳的最长时间。如果在这个时间内没有收到客户端的心跳,Nacos 服务器会将该实例标记为不健康。
  • ipDeleteTimeout:IP 删除超时时间,即当实例被标记为不健康后,Nacos 服务器等待多久才会将该实例从服务列表中删除。

NacosDiscoveryProperties 类中,有一个 ephemeral 属性:这个属性表示该服务实例是否是临时实例。如果 ephemeral 设置为 true,则该实例是临时实例,Nacos 客户端会定期发送心跳包;如果设置为 false,则该实例是非临时实例,Nacos 客户端不会发送心跳包,而是依赖于其他机制(如健康检查)来判断实例的健康状态。

image-20250719185006738

我们进入到其中的NacosDiscoveryHeartBeatConfiguration类中,可以发现,这是nacos心跳机制的详细配置类,专门用于初始化 Nacos 服务发现的心跳发布器,他会在在满足特定条件时,创建并注册一个心跳发布器实例,负责向 Nacos 服务器发送心跳信号,以维持服务实例的健康状态。

image-20250719185122690

而其中,该配置类通过 @Bean 方法定义了 NacosDiscoveryHeartBeatPublisher 实例,它是实际执行心跳逻辑的组件,它会根据 NacosDiscoveryProperties 中的配置(如心跳间隔、超时时间等),定期向 Nacos 服务器发送心跳请求@ConditionalOnMissingBean 确保全局只存在一个心跳发布器实例,避免重复发送心跳。

内部静态类 NacosDiscoveryHeartBeatCondition 定义了心跳机制生效的条件(通过 @Conditional 注解控制),满足以下任一条件时,心跳发布器才会被创建:

  • 条件 1:网关服务发现启用(spring.cloud.gateway.discovery.locator.enabled=true) 当使用 Spring Cloud Gateway 并开启服务发现时,需要心跳机制维护实例健康状态。
  • 条件 2:Spring Boot Admin 监控启用 当集成 Spring Boot Admin 时,需要通过心跳机制实时 Admin 感知实例存活状态。
  • 条件 3:显式开启 Nacos 心跳(spring.cloud.nacos.discovery.heart-beat.enabled=true) 直接通过配置强制启用心跳机制。

进入到NacosDiscoveryHeartBeatPublisher 类中,NacosDiscoveryHeartBeatPublisher 构造时依赖 NacosDiscoveryProperties,这意味着它会读取我们之前提到的心跳相关配置

该类实现了两个关键接口:

  • ApplicationEventPublisherAware:用于获取 Spring 的事件发布器,通过事件机制发送心跳信号。
  • SmartLifecycle:控制心跳任务的启动、停止等生命周期管理。
1
public class NacosDiscoveryHeartBeatPublisher implements ApplicationEventPublisherAware, SmartLifecycle 

心跳机制的核心实现,启动心跳任务(start() 方法)

1
2
3
4
5
6
7
8
9
10
11
public void start() {
// 线程安全的状态切换:如果当前未运行,则启动任务
if (this.running.compareAndSet(false, true)) {
log.info("Start nacos heartBeat task scheduler.");
// 定时执行心跳任务:间隔时间从配置中获取(watchDelay)
this.heartBeatFuture = this.taskScheduler.scheduleWithFixedDelay(
this::publishHeartBeat, // 执行的任务:发布心跳事件
Duration.ofMillis(this.nacosDiscoveryProperties.getWatchDelay()) // 间隔时间
);
}
}
  • 通过 compareAndSet 保证线程安全,避免重复启动。
  • 使用 scheduleWithFixedDelay 定时执行 publishHeartBeat 方法,间隔时间由 nacosDiscoveryProperties.getWatchDelay() 决定(默认 30 秒,可通过配置 spring.cloud.nacos.discovery.watch-delay 修改)。

发布心跳事件(publishHeartBeat() 方法)

1
2
3
4
5
6
public void publishHeartBeat() {
// 创建心跳事件(包含当前实例和心跳计数)
HeartbeatEvent event = new HeartbeatEvent(this, this.nacosHeartBeatIndex.getAndIncrement());
// 发布事件(由 Nacos 客户端监听并处理)
this.publisher.publishEvent(event);
}

NacosDiscoveryHeartBeatPublisher 内部会通过 Nacos 客户端 SDK 与服务器通信,这就联系到我们之前讲的心跳机制的原理内容了。该事件通过 Spring 的事件发布器 publisher 发布事件,后续会由 Nacos 客户端的监听器捕获并处理(实际向 Nacos 服务器发送心跳请求)。

就这么不断进入,你会发现回到了 Spring Boot 的事件管理机制相关的内容了

image-20250719185717993

Nacos 的服务发现机制

先说一点要清楚,Nacos服务的发现发生在什么时候,在 Spring Cloud Alibaba 整合 Nacos 的场景中,Nacos 服务发现主要发生在以下几个阶段:

  • 配置加载:应用启动时,Spring Boot 会加载配置文件(如application.yml)中关于 Nacos 的配置信息,这些配置信息用于初始化 Nacos 客户端
  • Nacos 客户端初始化:NacosDiscoveryAutoConfiguration等自动配置类会发挥作用,创建并初始化NacosDiscoveryProperties(封装 Nacos 发现相关配置属性)、NamingService(Nacos 提供的用于服务注册与发现的核心接口) 等相关组件。
  • 服务注册与发现触发:在初始化过程中,会判断当前应用是否需要注册到 Nacos(通过spring.cloud.nacos.discovery.register-enabled配置,默认开启)。如果需要注册,会将自身服务信息(如服务名、IP、端口等)注册到 Nacos 服务器。
  • 服务状态变更监听或者手动刷新:Nacos 客户端会持续监听 Nacos 服务器上服务实例状态的变化,如某个服务实例的启动、停止、健康状态改变等。一旦有变化,客户端会及时更新本地的服务列表缓存。
  • 服务调用阶段:当一个服务需要调用另一个服务时
    • 负载均衡前的发现
    • 手动编写业务从一个服务需要调用另一个服务的业务的时候

Nacos在进行服务发现的时候,回到老地方,spring-cloud-starter-alibaba-nacos-discovery,其中有这么一个包

image-20250719190523088

读源码我习惯从自动配置类阅读,能够了解前置条件,并且找到很多你想要的东西,所以我们来到NacosDiscoveryAutoConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* Nacos服务发现的自动配置类
*
* 该类负责自动配置Nacos服务发现所需的核心Bean,
* 当满足特定条件时(Nacos发现功能启用),会自动注册这些Bean到Spring容器中
*/
@Configuration(
proxyBeanMethods = false // 关闭@Configuration类的Bean方法代理,提高性能
)
// 条件注解:当服务发现功能启用时才会生效
@ConditionalOnDiscoveryEnabled
// 条件注解:当Nacos服务发现功能启用时才会生效
@ConditionalOnNacosDiscoveryEnabled
public class NacosDiscoveryAutoConfiguration {
/**
* 创建NacosDiscoveryProperties Bean
*
* 该Bean用于存储Nacos服务发现的配置属性,如服务地址、命名空间等
* @ConditionalOnMissingBean 注解表示当容器中没有该类型的Bean时才会创建
*
* @return NacosDiscoveryProperties实例
*/
@Bean
@ConditionalOnMissingBean
public NacosDiscoveryProperties nacosProperties() {
return new NacosDiscoveryProperties();
}

/**
* 创建NacosServiceDiscovery Bean
*
* 该Bean是Nacos服务发现的核心实现类,提供服务注册、发现等功能
* 依赖于NacosDiscoveryProperties(配置属性)和NacosServiceManager(Nacos服务管理器)
* @ConditionalOnMissingBean 注解表示当容器中没有该类型的Bean时才会创建
*
* @param discoveryProperties Nacos服务发现的配置属性
* @param nacosServiceManager Nacos服务管理器,用于管理Nacos客户端实例
* @return NacosServiceDiscovery实例
*/
@Bean
@ConditionalOnMissingBean
public NacosServiceDiscovery nacosServiceDiscovery(NacosDiscoveryProperties discoveryProperties, NacosServiceManager nacosServiceManager) {
return new NacosServiceDiscovery(discoveryProperties, nacosServiceManager);
}
}

接下来,我们阅读 NacosServiceManager的 Nacos 服务管理器看看,这个类是 Nacos 服务发现功能的核心管理类,负责创建、管理和销毁 Nacos 客户端实例(NamingServiceNamingMaintainService)。它通过单例模式和延迟初始化确保 Nacos 客户端的高效使用,同时处理配置变更和资源释放

来看看它引入了什么属性

1
2
3
4
5
6
// Nacos 服务发现配置属性(存储服务地址、命名空间等配置)
private NacosDiscoveryProperties nacosDiscoveryProperties;
// 服务发现客户端( volatile 保证多线程可见性)
private volatile NamingService namingService;
// 服务维护客户端(用于服务上下线、元数据管理等操作)
private volatile NamingMaintainService namingMaintainService;

NamingService 上面我们也说了,是 Nacos 服务发现的核心接口,提供服务注册、发现、心跳检测等功能。NacosServiceManager 通过延迟初始化双重检查锁确保其单例性,避免重复创建客户端实例。

1
2
3
4
5
6
7
8
public NamingService getNamingService() {
// 1. 第一次检查:如果实例未创建,则进入初始化流程
if (Objects.isNull(this.namingService)) {
// 2. 使用配置属性构建实例(从 nacosDiscoveryProperties 中获取配置)
this.buildNamingService(this.nacosDiscoveryProperties.getNacosProperties());
}
return this.namingService;
}

接下来就是构建 NamingService方法,然后通过 NacosFactory 创建底层的服务

image-20250720121834238

当首次调用 getNamingService() 时,会从 nacosDiscoveryProperties 中获取配置(如 Nacos 服务端地址 server-addr),通过 NacosFactory 创建客户端实例,并通过双重检查锁确保全局唯一。后续调用直接返回已创建的实例。

那么这里配置中心是如何体现出来的,可以看到接下来有个方法isNacosDiscoveryInfoChanged,就是用来做配置变更检测,Nacos 大部分业务类中都有这个配置检测变更的方法

1
2
3
4
5
public boolean isNacosDiscoveryInfoChanged(NacosDiscoveryProperties currentCache) {
// 比较当前配置与缓存的配置是否一致
return !Objects.isNull(this.nacosDiscoveryProperties)
&& !this.nacosDiscoveryProperties.equals(currentCache);
}
  • 若返回 true,表示配置已变更,上层逻辑会调用 nacosServiceShutDown() 销毁旧实例,再通过 getNamingService() 创建新实例。

最后剩下一个资源释放

来继续看NacosServiceDiscovery类,这就是Nacos服务发现功能的核心类了,它封装了与 Nacos 服务端交互的细节,向上层提供标准化的服务发现接口

image-20250720122317600

一上来就是两个属性,这俩属性上面也都说过了,Nacos 服务发现配置属性 和 Nacos 服务管理器,然后同构造函数注入,接下来就是getInstances 方法,这是服务实例的发现方法,是服务发现的核心

1
2
3
4
5
6
7
8
9
10
11
public List<ServiceInstance> getInstances(String serviceId) throws NacosException {
// 1. 从配置中获取服务分组(默认值:DEFAULT_GROUP)
String group = this.discoveryProperties.getGroup();

// 2. 调用 Nacos 客户端获取服务实例
// 参数说明:serviceId(服务名)、group(分组)、true(只返回健康实例)
List<Instance> instances = this.namingService().selectInstances(serviceId, group, true);

// 3. 转换为 Spring Cloud 标准的 ServiceInstance 列表
return hostToServiceInstanceList(instances, serviceId);
}

通过 namingService() 方法获取 NamingService 实例(复用之前讲解的 NacosServiceManager 管理的客户端)

1
2
3
4
private NamingService namingService() {
// 从 NacosServiceManager 中获取单例的 NamingService 客户端
return this.nacosServiceManager.getNamingService();
}

Nacos 原生返回 Instance 类型,需要转换为 Spring Cloud 标准的 ServiceInstance 接口实现,以便上层框架(如 Spring Cloud LoadBalancer)使用,虽然 Nacos 也有自己的负载均衡:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 批量转换方法
public static List<ServiceInstance> hostToServiceInstanceList(List<Instance> instances, String serviceId) {
List<ServiceInstance> result = new ArrayList(instances.size());
for(Instance instance : instances) {
// 逐个转换实例
ServiceInstance serviceInstance = hostToServiceInstance(instance, serviceId);
if (serviceInstance != null) {
result.add(serviceInstance);
}
}
return result;
}

// 单个实例转换方法
public static ServiceInstance hostToServiceInstance(Instance instance, String serviceId) {
// 过滤非健康或已禁用的实例
if (instance != null && instance.isEnabled() && instance.isHealthy()) {
NacosServiceInstance nacosServiceInstance = new NacosServiceInstance();
// 设置核心属性:IP、端口、服务ID
nacosServiceInstance.setHost(instance.getIp());
nacosServiceInstance.setPort(instance.getPort());
nacosServiceInstance.setServiceId(serviceId);
nacosServiceInstance.setInstanceId(instance.getInstanceId());

// 构建元数据(包含 Nacos 特有属性和实例自定义元数据)
Map<String, String> metadata = new HashMap();
metadata.put("nacos.instanceId", instance.getInstanceId()); // 实例唯一ID
metadata.put("nacos.weight", "" + instance.getWeight()); // 权重
metadata.put("nacos.healthy", "" + instance.isHealthy()); // 健康状态
metadata.put("nacos.cluster", "" + instance.getClusterName()); // 集群名
metadata.put("nacos.ephemeral", String.valueOf(instance.isEphemeral())); // 是否临时实例

// 合并实例自定义元数据(如服务版本、环境等)
if (instance.getMetadata() != null) {
metadata.putAll(instance.getMetadata());
}

nacosServiceInstance.setMetadata(metadata);

// 处理安全标记(如是否使用 HTTPS)
if (metadata.containsKey("secure")) {
boolean secure = Boolean.parseBoolean((String)metadata.get("secure"));
nacosServiceInstance.setSecure(secure);
}

return nacosServiceInstance;
} else {
return null; // 过滤不健康实例
}
}

可以看到

  • 仅保留 enabled(启用)且 healthy(健康)的实例,保证服务调用的可用性
  • 元数据(metadata)同时包含 Nacos 系统属性(如权重、集群)和用户自定义属性,兼顾框架需求和业务扩展
  • 适配 Spring Cloud 标准接口,使 Nacos 可以无缝集成到 Spring Cloud 生态中

然后还有一个就是获取服务列表,getServices 方法用于获取 Nacos 服务端注册的所有服务名称,流程如下:

1
2
3
4
5
6
7
8
9
10
11
public List<String> getServices() throws NacosException {
// 1. 获取配置的服务分组
String group = this.discoveryProperties.getGroup();

// 2. 调用 Nacos 客户端获取服务列表
// 参数说明:1(页码)、Integer.MAX_VALUE(每页条数,获取全部)、group(分组)
ListView<String> services = this.namingService().getServicesOfServer(1, Integer.MAX_VALUE, group);

// 3. 返回服务名称列表(从 ListView 中提取 data 字段)
return services.getData();
}

总结一下:

  1. 初始化阶段
    • NacosServiceDiscovery 依赖 NacosServiceManager 获取 NamingService 客户端实例
    • NamingService 通过 NacosFactory 基于配置(NacosDiscoveryProperties)创建,与 Nacos 服务端建立连接
  2. 服务发现阶段
    • 应用调用 getInstances(String serviceId) 方法,传入目标服务名
    • 方法从配置中获取服务分组,调用 NamingService.selectInstances 向 Nacos 服务端发起查询
    • Nacos 服务端返回该服务下所有健康实例(Instance 列表)
    • 客户端将 Instance 转换为 Spring Cloud 标准的 ServiceInstance 列表,过滤不健康实例并补充元数据
  3. 服务列表查询阶段
    • 调用 getServices() 方法时,通过 NamingService.getServicesOfServer 获取所有服务名称
    • 返回结果供控制台或监控系统展示服务注册情况

Nacos 服务注册与订阅的详细使用

nacos 的安装与配置

https://github.com/alibaba/nacos/releases

下载之后进入到bin文件目录,点击startup.cmd,启动项目:

image-20250710143458140

需要以单机模式启动

1
2
# 单机模式
startup.cmd -m standalone

需要让你输入一些 nacos 的安全配置,用于不同场景的身份验证和权限控制。分别是生成和验证 JWT 的,用于 Nacos Server 集群间通信时的身份验证和服务身份标识,用于标识当前 Nacos Server 的身份(如集群中的唯一 ID)。

image-20250710143842460

在浏览器地址输入http://localhost:8848/nacos/index.html,默认 nacos 账号密码都是 nacos

至此,nacos已经启动成功,我们可以访问地址: http://169.254.27.253:8080/index.html 访问nacos的页面。如果需要登录,默认的用户名和密码都是nacos

image-20250710235629676

打开mysql数据库,进行配置,打开Nacos配置]文件 ${nacos-server.path}/conf/application.properties

image-20250719183639366

因为 nacos 也是一个 Spring 项目,所以说配置文件肯定也是 application.properties,设置正确的数据库平台为MySQL

1
spring.datasource.platform=mysql

配置数据库连接信息

1
2
3
db.url=jdbc:mysql://你的数据库地址:端口/数据库名?useUnicode=true&characterEncoding=utf8&autoReconnect=true&serverTimezone=UTC
db.user=你的数据库用户名
db.password=你的数据库密码

Nacos 在 Spring Cloud 中的服务发现

创建服务提供者模块 nacos-provider,服务提供者是提供接口的微服务,需注册到 Nacos。

添加依赖

1
2
3
4
5
6
7
8
9
10
11
12
<dependencies>
<!-- Spring Web(提供 HTTP 接口) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Nacos 服务注册发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>

配置文件配置,服务消费者和提供者是差不多的,都需要有这个配置,剩下的就是你自己编辑了

1
2
3
4
5
6
7
8
9
# 应用名称,也是注册到Nacos的服务名
spring.application.name=nacos-consumer
# 应用端口号
server.port=8088

# Nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
# 命名空间,默认为public
spring.cloud.nacos.discovery.namespace=public

写一个控制器,然后对应的主类也要加上服务发现的注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 服务提供者控制器
* 提供REST API接口供消费者调用
*/
@RestController
@RequestMapping("/provider")
public class ProviderController {

/**
* 注入服务端口号,用于识别是哪个实例提供的服务
*/
@Value("${server.port}")
private String serverPort;

/**
* 提供一个简单的问候服务
* @param name 名称参数
* @return 问候语
*/
@GetMapping("/hello/{name}")
public String hello(@PathVariable String name) {
return "你好," + name + "!这是来自端口号为 " + serverPort + " 的服务提供者的响应";
}

/**
* 提供服务实例信息
* @return 服务信息
*/
@GetMapping("/info")
public String info() {
return "服务提供者实例,端口号:" + serverPort;
}
}

创建服务消费者模块 nacos-consumer,服务消费者通过 Nacos 发现 Provider 并调用其接口。依赖是一样的,写一个消费者的控制器,并且把主启动类也加上对应的服务发现注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/**
* 服务消费者控制器
* 通过Nacos服务发现调用服务提供者的接口
*/
@RestController
@RequestMapping("/consumer")
public class ConsumerController {

/**
* 注入RestTemplate,用于发起HTTP请求
*/
@Autowired
private RestTemplate restTemplate;

/**
* 注入DiscoveryClient,用于获取服务实例信息
*/
@Autowired
private DiscoveryClient discoveryClient;

/**
* 服务提供者的应用名称
*/
private static final String SERVICE_NAME = "nacos-provider";

/**
* 通过负载均衡方式调用服务提供者的hello接口
* @param name 名称参数
* @return 服务提供者返回的结果
*/
@GetMapping("/hello/{name}")
public String hello(@PathVariable String name) {
// 使用服务名称调用,RestTemplate会根据负载均衡策略选择一个实例
return restTemplate.getForObject("http://" + SERVICE_NAME + "/provider/hello/" + name, String.class);
}

/**
* 获取服务提供者的信息
* @return 服务提供者的信息
*/
@GetMapping("/provider-info")
public String getProviderInfo() {
return restTemplate.getForObject("http://" + SERVICE_NAME + "/provider/info", String.class);
}

/**
* 获取所有服务提供者实例的信息
* @return 所有服务提供者实例的信息
*/
@GetMapping("/discovery")
public Object discovery() {
// 获取服务列表
List<String> services = discoveryClient.getServices();
StringBuilder sb = new StringBuilder();
sb.append("所有服务: ").append(services).append("<br/>");

// 获取指定服务的所有实例
List<ServiceInstance> instances = discoveryClient.getInstances(SERVICE_NAME);
sb.append(SERVICE_NAME).append("服务的实例数量: ").append(instances.size()).append("<br/>");

// 输出每个实例的详细信息
for (ServiceInstance instance : instances) {
sb.append("ID: ").append(instance.getInstanceId())
.append(", Host: ").append(instance.getHost())
.append(", Port: ").append(instance.getPort())
.append(", URI: ").append(instance.getUri())
.append("<br/>");
}

return sb.toString();
}
}

别忘了把 Nacos 服务跑起来

image-20250720135020472

先启动服务提供模块,启动后,服务会注册到Nacos,再启动服务消费模块,启动后,服务也会注册到Nacos

image-20250720135335215
image-20250720135359470
image-20250720135444385
image-20250720135550702

可以看到这些模块正常工作,也是可以被正常注册和发现的,然后在这里我们可以进行服务管理、配置管理等操作。

这只是最基本的 Spring Cloud 整合 Nacos 注册中心的简单实例,之后我们的各种演示都会基于这个基础上进行