Nacos(1)——微服务下的服务注册
我们在使用Eureka时,通常会使用如下注解:
1@EnableDiscoveryClient
2@EnableEurekaClient
@EnableEurekaClient是Netfix单独为Eureka的Client端开发的注解,位于spring-cloud-netflix-eureka-client包中
@EnableDiscoveryClient是Spring Cloud的一个原生注解,位于spring-cloud-commons包中,可以视为一种规范。
接下来我们将分析Spring Cloud Alibaba套件是如何进行服务发现的。
Spring Cloud Alibaba将在本篇文章中以SCA代替
场景搭建
启动器
1@EnableDiscoveryClient
2@SpringBootApplication
3public class NacosClientThreeBoostrap {
4
5 public static void main(String[] args) {
6 SpringApplication.run(NacosClientThreeBoostrap.class);
7 }
8}
application.yml:
1server:
2 port: 40010
3spring:
4 application:
5 name: sunshine-taurus
6 cloud:
7 nacos:
8 discovery:
9 server-addr: 127.0.0.1:8848
10management:
11 endpoints:
12 web:
13 exposure:
14 include: \*
maven依赖:
1<dependencies>
2 <dependency>
3 <groupId>org.springframework.boot</groupId>
4 <artifactId>spring-boot-starter-web</artifactId>
5 </dependency>
6 <dependency>
7 <groupId>org.springframework.cloud</groupId>
8 <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
9 </dependency>
10 <dependency>
11 <groupId>org.springframework.boot</groupId>
12 <artifactId>spring-boot-starter-actuator</artifactId>
13 </dependency>
14</dependencies>
@EnableDiscoveryClient注解去向分析
SCA对于这个注解执行的非常的简单粗暴:
1org.springframework.cloud.client.discovery.EnableDiscoveryClient=\
2org.springframework.cloud.alibaba.nacos.NacosDiscoveryClientAutoConfiguration
直接指到了核心实现类:
1@Configuration
2@ConditionalOnMissingBean(DiscoveryClient.class)
3@ConditionalOnNacosDiscoveryEnabled
4@EnableConfigurationProperties
5public class NacosDiscoveryClientAutoConfiguration {
6
7 @Bean
8 public DiscoveryClient nacosDiscoveryClient() {
9 // 实例化NacosDiscoveryClient
10 return new NacosDiscoveryClient();
11 }
12
13 @Bean
14 @ConditionalOnMissingBean
15 public NacosDiscoveryProperties nacosProperties() {
16 // 实例化NacosDiscoveryProperties
17 return new NacosDiscoveryProperties();
18 }
19}
链路解释:
实例化NacosDiscoveryClient,也就是实例化一个我们上报到Nacos服务发现的客户端实例。
实例化NacosDiscoveryClient属性Bean,实例化NacosDiscoveryClient会需要。
Bean: NacosDiscoveryProperties
实例化的核心:
1public void init() throws SocketException {
2
3 serverAddr = Objects.toString(serverAddr, "");
4 endpoint = Objects.toString(endpoint, "");
5 namespace = Objects.toString(namespace, "");
6 logName = Objects.toString(logName, "");
7
8 if (StringUtils.isEmpty(ip)) {
9 // 探测本机的IP地址
10 if (StringUtils.isEmpty(networkInterface)) {
11 ip = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
12 }
13 else {
14 NetworkInterface netInterface = NetworkInterface
15 .getByName(networkInterface);
16 if (null == networkInterface) {
17 throw new IllegalArgumentException(
18 "no such interface " + networkInterface);
19 }
20
21 Enumeration<InetAddress> inetAddress = netInterface.getInetAddresses();
22 while (inetAddress.hasMoreElements()) {
23 InetAddress currentAddress = inetAddress.nextElement();
24 if (currentAddress instanceof Inet4Address
25 && !currentAddress.isLoopbackAddress()) {
26 ip = currentAddress.getHostAddress();
27 break;
28 }
29 }
30
31 if (StringUtils.isEmpty(ip)) {
32 throw new RuntimeException("cannot find available ip from"
33 + " network interface " + networkInterface);
34 }
35
36 }
37 }
38 // 重写本地变量
39 this.overrideFromEnv(environment);
40}
链路解释:
重写本地变量。
继续看下NacosDiscoveryProperties#overridFromEnv()方法:
1public void overrideFromEnv(Environment env) {
2
3 if (StringUtils.isEmpty(this.getServerAddr())) {
4 this.setServerAddr(env
5 .resolvePlaceholders("${spring.cloud.nacos.discovery.server-addr:}"));
6 }
7 if (StringUtils.isEmpty(this.getNamespace())) {
8 this.setNamespace(env
9 .resolvePlaceholders("${spring.cloud.nacos.discovery.namespace:}"));
10 }
11 if (StringUtils.isEmpty(this.getAccessKey())) {
12 this.setAccessKey(env
13 .resolvePlaceholders("${spring.cloud.nacos.discovery.access-key:}"));
14 }
15 if (StringUtils.isEmpty(this.getSecretKey())) {
16 this.setSecretKey(env
17 .resolvePlaceholders("${spring.cloud.nacos.discovery.secret-key:}"));
18 }
19 if (StringUtils.isEmpty(this.getLogName())) {
20 this.setLogName(
21 env.resolvePlaceholders("${spring.cloud.nacos.discovery.log-name:}"));
22 }
23 if (StringUtils.isEmpty(this.getClusterName())) {
24 this.setClusterName(env.resolvePlaceholders(
25 "${spring.cloud.nacos.discovery.clusterName-name:}"));
26 }
27 if (StringUtils.isEmpty(this.getEndpoint())) {
28 this.setEndpoint(
29 env.resolvePlaceholders("${spring.cloud.nacos.discovery.endpoint:}"));
30 }
31}
核心思想是从Environment中获取这些必须值并赋值。
Bean: NacosDiscoveryClient
根据@EnableDiscoveryClient的语义,想要实现一个客户端的服务发现,需要实现DiscoveryClient接口:
1public interface DiscoveryClient {
2
3 /**
4 * 一个可读的实现描述,用于健康检查
5 * @return the description
6 */
7 String description();
8
9 /**
10 * 获取服务serviceId下所有服务实例
11 * @param serviceId the serviceId to query
12 * @return a List of ServiceInstance
13 */
14 List<ServiceInstance> getInstances(String serviceId);
15
16 /**
17 * 返回所有的服务serviceId
18 * @return all known service ids
19 */
20 List<String> getServices();
21
22}
NacosDiscoveryClient也实现了它的接口,我们先看下对方法的具体实现。
NacosDiscoveryClient#getInstantces():
1@Override
2public List<ServiceInstance> getInstances(String serviceId) {
3 try {
4 // 获取该服务serviceId下的实例信息
5 List<Instance> instances = discoveryProperties.namingServiceInstance()
6 .selectInstances(serviceId, true);
7 // 封装为ServiceInstance实例
8 return hostToServiceInstanceList(instances, serviceId);
9 }
10 ...
11}
链路分析:
获取该服务serviceId下的实例信息。
封装为ServiceInstance实例。
调用NacosDiscoveryProperties#namingServiceInstance()时:
1public NamingService namingServiceInstance() {
2
3 if (null != namingService) {
4 return namingService;
5 }
6
7 Properties properties = new Properties();
8 properties.put(SERVER_ADDR, serverAddr);
9 properties.put(NAMESPACE, namespace);
10 properties.put(UtilAndComs.NACOS_NAMING_LOG_NAME, logName);
11 properties.put(ENDPOINT, endpoint);
12 properties.put(ACCESS_KEY, accessKey);
13 properties.put(SECRET_KEY, secretKey);
14 properties.put(CLUSTER_NAME, clusterName);
15 properties.put(NAMING_LOAD_CACHE_AT_START, namingLoadCacheAtStart);
16
17 try {
18 // 根据服务信息构建命名服务
19 namingService = NacosFactory.createNamingService(properties);
20 return namingService;
21 }
22 catch (Exception e) {
23 LOGGER.error("create naming service error!properties={},e=,", this, e);
24 return null;
25 }
26}
SCA会根据我们在application.yml中使用的服务信息,构建当前服务的命名服务。
然后通过反射的形式,通过构造方法,实例化一个NamingService,NacosNamingService#NacosNamingService():
1public NacosNamingService(Properties properties) {
2 Properties properties = new Properties();
3 // 设置Nacos服务节点集群属性
4 properties.setProperty(PropertyKeyConst.SERVER_ADDR, serverList);
5 // 初始化NacosNamingService
6 init(properties);
7}
8
9private void init(Properties properties) {
10 // 解析当前service的namespace
11 namespace = InitUtils.initNamespaceForNaming(properties);
12 // 初始化Nacos服务节点的域名地址
13 initServerAddr(properties);
14 InitUtils.initWebRootContext();
15 // NamingService的文件缓存地址
16 initCacheDir();
17 // 初始化日志对象
18 initLogName(properties);
19 // 创建事件驱动器
20 eventDispatcher = new EventDispatcher();
21 // 创建一个命名服务的代理
22 serverProxy = new NamingProxy(namespace, endpoint, serverList, properties);
23 // 利用命名服务的代理创建对应的心跳响应
24 beatReactor = new BeatReactor(serverProxy, initClientBeatThreadCount(properties));
25 // 创建一个Host响应
26 hostReactor = new HostReactor(eventDispatcher, serverProxy, cacheDir, isLoadCacheAtStart(properties), initPollingThreadCount(properties));
27}
链路分析:
初始化命名服务,目的在于设置一些参数的初始值。
赋值属性,根据服务本身的启动参数,对命名服务相关参数进行设置。
创建事件驱动器,事件驱动器功能是进行通知和监听事件。
创建命名服务代理,目的在于创建和Nacos服务端建立HTTP连接的代理。
创建心跳响应,用于建立和Nacos服务端所有节点的心跳连接,心跳连接的建立依托于步骤4建立的服务代理。
创建一个Host响应,用于发起和Nacos服务端节点的注册,解绑等请求,请求的建立同样依托于步骤4建立的服务代理。
我们可以看下故障转移。
故障转移
故障转移其实只有两个私有的成员变量:failoverDir和hostReactor。
failoverDir是故障转移的日志记录。
hostReactor就是我们在上文创建的发起请求的响应式处理器。
1public void init() {
2 // 用于切换刷新写入文件的任务调度
3 executorService.scheduleWithFixedDelay(new SwitchRefresher(), 0L, 5000L, TimeUnit.MILLISECONDS);
4 // 用于每日统计的写文件的任务调度
5 executorService.scheduleWithFixedDelay(new DiskFileWriter(), 30, DAY_PERIOD_MINUTES, TimeUnit.MINUTES);
6 ...
7 // 后备目录和日志文件的任务调度
8}
链路分析:
根据上一次变更的时间戳来判断是否需要写入当次刷新的日志文件。
写入每天文件到磁盘中。
如果当前目录不存在,创建后备日志文件和目录。
我们是从NacosDiscoveryClient#getInstances()方法到达这里的。
接下来我们继续看NacosNamingService#selectInstances()方法:
1@Override
2public List<Instance> selectInstances(String serviceName, String groupName, List<String> clusters, boolean healthy, boolean subscribe) throws NacosException {
3
4 ServiceInfo serviceInfo;
5 if (subscribe) {
6 serviceInfo = hostReactor.getServiceInfo(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ","));
7 } else {
8 serviceInfo = hostReactor.getServiceInfoDirectlyFromServer(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ","));
9 }
10 return selectInstances(serviceInfo, healthy);
11}
这里会存在一个订阅的概念,订阅的概念其实是一个异步模型,即我们每次去自己的缓存中获取指定服务列表,而服务列表的更新,则是依靠任务调度完成的。
我们发起的请求,一般会以订阅的方式进行。
而上面的实现则是根据订阅方式,决定是从缓存中直接取出指定service的服务列表,还是去Nacos服务端获取对应service的服务列表。
接下来我们继续看NacosNamingService#selectInstances()方法:
1private List<Instance> selectInstances(ServiceInfo serviceInfo, boolean healthy) {
2 List<Instance> list;
3 if (serviceInfo == null || CollectionUtils.isEmpty(list = serviceInfo.getHosts())) {
4 return new ArrayList<Instance>();
5 }
6
7 Iterator<Instance> iterator = list.iterator();
8 // 判断服务实例的状态
9 while (iterator.hasNext()) {
10 Instance instance = iterator.next();
11 if (healthy != instance.isHealthy() || !instance.isEnabled() || instance.getWeight() <= 0) {
12 iterator.remove();
13 }
14 }
15
16 return list;
17}
接下来就是判断服务实例的状态,移除处于非健康,不可用,没有分配权重的服务实例。
NacosDiscoverClient我们分析完成了,但是感觉是不是跑题了,因为我们看到的是怎么获取实例,但是我们是什么时候注册上的呢?
这其实就是一个思想误区,认为DiscoveryClient既要完成服务注册的功能,还要进行服务实例获取。
接下来我将简略的讲一下,Spring Cloud处理的核心体系。
Spring Cloud服务注册的体系
在容器初始化时,会有onRefresh()和finishRefresh()两个方法。
onRefresh()是对容器进行初始化。
finishRefresh()是结束容器化,比如开始WebServer,发布ApplicationEvent操作等。
注册就是通过发布ApplicationEvent进行的。
那么消费的就是AbstractAutoServiceRegistration类,AbstractAutoServiceRegistration#bind():
1@EventListener(WebServerInitializedEvent.class)
2public void bind(WebServerInitializedEvent event) {
3 ApplicationContext context = event.getApplicationContext();
4 // 如果是服务端,不进行注册
5 if (context instanceof ConfigurableWebServerApplicationContext) {
6 if ("management".equals(
7 ((ConfigurableWebServerApplicationContext) context).getServerNamespace())) {
8 return;
9 }
10 }
11 // 设置端口
12 this.port.compareAndSet(0, event.getWebServer().getPort());
13 // 启动服务注册
14 this.start();
15}
链路梳理:
如果是服务端,不进行注册。
设置端口,port端口比较重要,避免并发情况下对port写错。
进行服务注册。
继续看下服务注册的步骤,AbstractAutoServiceRegistration#start():
1public void start() {
2 // 校验服务发现状态
3 if (!isEnabled()) {
4 if (logger.isDebugEnabled()) {
5 logger.debug("Discovery Lifecycle disabled. Not starting");
6 }
7 return;
8 }
9
10 // 没有注册,才会注册
11 if (!this.running.get()) {
12 // 获取注册信息,并发布注册前事件
13 this.context.publishEvent(new InstancePreRegisteredEvent(this, getRegistration()));
14 // 注册
15 register();
16 // 是否应该注册服务者
17 if (shouldRegisterManagement()) {
18 registerManagement();
19 }
20 // 获取注册信息,并发布注册后事件
21 this.context.publishEvent(
22 new InstanceRegisteredEvent<>(this, getConfiguration()));
23 // 注册状态更改为已注册。
24 this.running.compareAndSet(false, true);
25 }
26
27}
链路分析:
校验服务发现状态。
没有注册,才会进行注册。
获取注册信息,并发布注册前事件。
注册。
是否应该注册服务则。
获取注册信息,并发布注册后事件。
注册状态更改为已注册。
从类名分析,我们不难发现,AbstractAutoServiceRegistration应该是一个超类,我们之前分析的是抽象模型,那么调用register的时候会先走派生类,NacosAutoServiceRegistration#register():
1@Override
2protected void register() {
3 // 根据当前服务发现的属性判断是否可以注册
4 if (!this.registration.getNacosDiscoveryProperties().isRegisterEnabled()) {
5 LOGGER.debug("Registration disabled.");
6 return;
7 }
8 // 如果注册器里面的端口是不正常的,设置端口号
9 if (this.registration.getPort() < 0) {
10 this.registration.setPort(getPort().get());
11 }
12 // 调用超类的注册接口
13 super.register();
14}
链路分析:
根据当前服务发现的属性判断是否可以注册。
如果注册器里面的端口是不正常的,设置端口。
调用超类的注册接口。
继续,:
1protected void register() {
2 this.serviceRegistry.register(getRegistration());
3}
链路分析:
进行服务注册。
我们看到服务注册对象ServiceRegistry是个接口,那么继续看实现,NacosServiceRegistry#register():
1@Override
2public void register(NacosRegistration registration) {
3 // 再次判断是否可注册
4 if (!registration.isRegisterEnabled()) {
5 logger.info("Nacos Registration is disabled...");
6 return;
7 }
8 // 如果没有服务ServiceId可供注册,不注册
9 if (StringUtils.isEmpty(registration.getServiceId())) {
10 logger.info("No service to register for nacos client...");
11 return;
12 }
13 // 获取命名服务
14 NamingService namingService = registration.getNacosNamingService();
15 // 获取服务ServiceId
16 String serviceId = registration.getServiceId();
17
18 // 创建注册实例
19 Instance instance = new Instance();
20 instance.setIp(registration.getHost());
21 instance.setPort(registration.getPort());
22 instance.setWeight(registration.getRegisterWeight());
23 instance.setCluster(new Cluster(registration.getCluster()));
24 instance.setMetadata(registration.getMetadata());
25
26 try {
27 // 进行服务注册
28 namingService.registerInstance(serviceId, instance);
29 logger.info("nacos registry, {} {}:{} register finished", serviceId,
30 instance.getIp(), instance.getPort());
31 }
32 catch (Exception e) {
33 ...
34 }
35}
链路分析:
再次判断当前服务是否可注册。
如果没有服务ServiceId,就不需要注册。
获取命名服务,命名服务在实例化EnableDiscoveryClient的时创建。
获取服务ServiceId。
创建注册实例Instance对象。
进行服务注册。
看到NacosNamingService,从前面的链路分析来看,我们接下来应该是就是使用HostReactor进行注册了,
NacosNamingService#registerInstance():
1@Override
2public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
3 // 如果是临时节点,也就是我们需要注册的服务节点,则需要客户端自己负责心跳上报
4 if (instance.isEphemeral()) {
5 BeatInfo beatInfo = new BeatInfo();
6 beatInfo.setServiceName(NamingUtils.getGroupedName(serviceName, groupName));
7 beatInfo.setIp(instance.getIp());
8 beatInfo.setPort(instance.getPort());
9 beatInfo.setCluster(instance.getClusterName());
10 beatInfo.setWeight(instance.getWeight());
11 beatInfo.setMetadata(instance.getMetadata());
12 beatInfo.setScheduled(false);
13 beatInfo.setPeriod(instance.getInstanceHeartBeatInterval());
14 // 添加心跳任务
15 beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);
16 }
17 // 通过NamingProxy向Nacos服务端发起注册请求
18 serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);
19}
链路分析:
如果注册的是我们的服务节点,则节点类型是临时节点,需要客户端自己负责心跳上报,心跳上报是由任务调度器来完成的。
通过NamingProxy向Nacos服务端发起服务注册请求。
接下来,NamingProxy#registerService():
1public void registerService(String serviceName, Instance instance) throws NacosException {
2 NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}",
3 namespaceId, serviceName, instance);
4 // 服务注册请求参数
5 final Map<String, String> params = new HashMap<String, String>(9);
6 params.put(CommonParams.NAMESPACE_ID, namespaceId);
7 params.put(CommonParams.SERVICE_NAME, serviceName);
8 params.put(CommonParams.GROUP_NAME, groupName);
9 params.put(CommonParams.CLUSTER_NAME, instance.getClusterName());
10 params.put("ip", instance.getIp());
11 params.put("port", String.valueOf(instance.getPort()));
12 params.put("weight", String.valueOf(instance.getWeight()));
13 params.put("enable", String.valueOf(instance.isEnabled()));
14 params.put("healthy", String.valueOf(instance.isHealthy()));
15 params.put("ephemeral", String.valueOf(instance.isEphemeral()));
16 params.put("metadata", JSON.toJSONString(instance.getMetadata()));
17 // 发起HTTP请求,请求方法:/nacos/v1/ns/instance
18 reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, HttpMethod.POST);
19}
链路分析:
将Instance实例对象转化为HashMap参数对象,调用HttpClient的请求方法,进行注册。由于是注册行为,所以Http请求方法为PUT。
这样,我们的服务发现的实例化与注册流程基本打通了。
聪明的你们肯定会发现,在刚刚的注册链路中,有两个Bean是不知道何时进行实例化的。
NacosServiceRegistry
追根朔源,我们发现NacosServiceRegistry是在NacosDiscoveryAutoConfiguration类中进行初始化的。
首先,NacosDiscoveryAutoConfiguration类头会有一系列的校验规则:
1@Configuration
2@EnableConfigurationProperties
3@ConditionalOnNacosDiscoveryEnabled
4@ConditionalOnClass(name = "org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent")
5@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
6@AutoConfigureBefore({ AutoServiceRegistrationAutoConfiguration.class,
7 NacosDiscoveryClientAutoConfiguration.class })
校验规则分析:
是个Configuration配置类,意味着接下来需要注入多个Bean。
允许使用配置属性信息。
允许进行Nacos服务发现。
在ServletWebServerInitializedEvent实例化之后实例化。
校验自动注册规则,默认值为true。
在实例化之前,先完成AutoServiceRegistrationAutoConfiguration和NacosDiscoveryClientAutoConfiguration的配置。
峰回路转,我们看到了NacosDiscoveryClientAutoConfiguration类。
再看NacosDiscoveryAutoConfiguration中具体的Bean,逐个进行分析:
1@Bean
2public NacosServiceRegistry nacosServiceRegistry() {
3 return new NacosServiceRegistry();
4}
第一个是实现ServiceRegistry接口的NacosServiceRegistry,我们在注册链路中刚刚用过它的register()方法。
它共有两个核心方法,分别是register()和deregister()。
1@Override
2public void deregister(NacosRegistration registration) {
3 if (instance.isEphemeral()) {
4 beatReactor.removeBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), instance.getIp(), instance.getPort());
5 }
6 serverProxy.deregisterService(NamingUtils.getGroupedName(serviceName, groupName), instance);
7}
调用链路:
如果需要解绑的实例是我们的服务实例,则需要移除后台正在进行调度的心跳上报任务。
通过NamingProxy向Nacos服务端发起解绑服务请求。
下一个Bean:
1@Bean
2@ConditionalOnBean(AutoServiceRegistrationProperties.class)
3public NacosRegistration nacosRegistration() {
4 return new NacosRegistration();
5}
这个对象也很熟悉,在注册链路和撤销注册链路中,这个对象就是入参。
我们在这里进行一些默认属性的配置:
1@PostConstruct
2public void init() {
3
4 Environment env = context.getEnvironment();
5 Integer managementPort = ManagementServerPortUtils.getPort(context);
6 if (null != managementPort) {
7 Map<String, String> metadata = nacosDiscoveryProperties.getMetadata();
8 metadata.put(MANAGEMENT_PORT, managementPort.toString());
9 String contextPath = env
10 .getProperty("management.server.servlet.context-path");
11 String address = env.getProperty("management.server.address");
12 if (!StringUtils.isEmpty(contextPath)) {
13 metadata.put(MANAGEMENT_CONTEXT_PATH, contextPath);
14 }
15 if (!StringUtils.isEmpty(address)) {
16 metadata.put(MANAGEMENT_ADDRESS, address);
17 }
18 }
19}
因为我们需要在NacosRegistration中使用NacosDiscoveryProperties属性,所以我们就先需要进行NacosDiscoveryClientAutoConfiguration的注入。
下一个Bean:
1@Bean
2@ConditionalOnBean(AutoServiceRegistrationProperties.class)
3public NacosAutoServiceRegistration nacosAutoServiceRegistration(
4 NacosServiceRegistry registry,
5 AutoServiceRegistrationProperties autoServiceRegistrationProperties,
6 NacosRegistration registration) {
7 return new NacosAutoServiceRegistration(registry,
8 autoServiceRegistrationProperties, registration);
9}
这个Bean出现在注册链路的前端,在容器执行finishRefresh()并发送ApplicationEvent事件时,此时,NacosAutoServiceRegistration是AbstractAutoServiceRegistration的派生类。
至此,结束SCA的服务注册源码分析。
Spring Cloud和Spring Boot是两条链路,开发进度不同,故不要使用最新的Spring Boot版本,应该框架建议的Spring Boot版本,当前SCA代码中Spring Boot版本为2.1.1.RELEASE。
总结
我们以更直观的形式进行总结:
Nacos服务注册流程