Nacos总结
角色说明
Nacos Server:Nacos服务提供者,里面包含的Open API是功能访问入口,Conig Service、Naming Service 是Nacos提供的配置服务、命名服务模块。Consitency Protocol是一致性协议,用来实现Nacos集群节点的数据同步,这里使用的是Raft算法。
Provider APP:服务提供者
Consumer APP:服务消费者
Nacos 的注册中心和配置中心采用了不同的一致性协议,以适应各自的业务场景需求。以下是具体分析:
一、注册中心的一致性协议
Nacos 注册中心主要用于服务实例的注册与发现,其核心需求是高可用性和最终一致性,具体协议如下:
AP 模式(默认):
基于 Distributed Edition of Consistent Hashing(DCH)算法:将服务实例均匀分布到不同节点,通过异步复制实现数据最终一致性。
场景:适用于服务实例频繁变更(如上下线)的场景,允许短暂的不一致,但保证服务发现的可用性。
CP 模式(可选):
基于 Raft 协议:通过选举主节点,确保数据强一致性(所有节点数据完全同步)。
场景:适用于对数据一致性要求极高的场景(如金融交易服务),但可能因主节点故障导致短暂不可用。
二、配置中心的一致性协议
Nacos 配置中心用于管理应用配置,核心需求是数据强一致性和持久化,主要采用:
CP 模式(基于 Raft 协议):
配置数据的写入必须通过主节点,并同步到多数节点后才返回成功,确保数据强一致性。
场景:配置变更需要全局一致(如动态切换熔断策略),避免因配置不一致导致服务异常。
持久化机制:
配置数据会持久化到数据库(如 MySQL),即使节点故障也能通过数据恢复保证一致性。
三、协议差异对比
维度 注册中心(服务发现) 配置中心(配置管理)
核心需求 高可用性、服务快速发现 数据强一致性、配置持久化
默认协议 AP 模式(DCH 算法 + 异步复制) CP 模式(Raft 协议 + 同步复制)
一致性级别 最终一致性 强一致性
故障影响 短暂不一致,但服务发现不受阻 主节点故障时配置写入会阻塞
典型场景 微服务实例动态上下线、流量负载均衡 全局配置变更、配置灰度发布
四、总结
Nacos 根据业务场景的不同,为注册中心和配置中心设计了差异化的一致性协议:
注册中心:以 AP 为主,优先保证可用性,允许最终一致性,适应服务实例的动态变化。
配置中心:以 CP 为主,确保配置数据强一致性和持久化,避免因配置不一致导致服务风险。
这种设计兼顾了微服务架构中 “服务发现” 和 “配置管理” 的不同需求,体现了 Nacos 在分布式系统中的灵活性和专业性。
注册中心的原理
① 服务实例在启动时注册到服务注册表,并在关闭时注销
② 服务消费者查询服务注册表,获得可用实例
③ 服务注册中心需要调用服务实例的健康检查API来验证它是否能够处理请求
注册中心集群部署,支持AP和CP。
AP使用Distro协议。
CP使用Raft协议。
Nacos即能保证CP,也能保证AP,具体看如何配置,Nacos中的注册中心能保证CP或AP,Nacos中的配置中心其实没什么CP成AP,因为配置中心的数据是存在MySQL数据库中。
只有注册中心的数据需要进行集群节点之间的同步,从而涉及到是CP还是AP,默认是AP模式,当集群部署,网络分区故障时,需要保证可用性,牺牲一致性。

Spring Cloud 完成注册的时机
在Spring-Cloud-Common包中有一个类org.springframework.cloud. client.serviceregistry .ServiceRegistry ,它是Spring Cloud提供的服务注册的标准。集成到Spring Cloud中实现服务注册的组件,都会实现该接口。

该接口有一个实现类是NacoServiceRegistry。
Spring Cloud集成Nacos的实现过程:
在spring-cloud-commons包的META-INF/spring.factories中包含自动装配的配置信息如下:

其中AutoServiceRegistrationAutoConfiguration就是服务注册相关的配置类:

在AutoServiceRegistrationAutoConfiguration配置类中,可以看到注入了一个AutoServiceRegistration实例,该类的关系图如下所示。

NacosServiceRegistry.register方法进行服务注册。
NacosServiceRegistry的实现
在NacosServiceRegistry.registry方法中,调用了Nacos Client SDK中的namingService.registerInstance完成服务的注册。

跟踪 NacosNamingService的registerInstance()方法:


通过beatReactor.addBeatInfo()创建心跳信息实现健康检测, Nacos Server必须要确保注册的服务实例是健康的,而心跳检测就是服务健康检测的手段。
serverProxy.registerService()实现服务注册
心跳机制:


从上述代码看,所谓心跳机制就是客户端通过schedule定时向服务端发送一个数据包 ,然后启动-个线程不断检测服务端的回应,如果在设定时间内没有收到服务端的回应,则认为服务器出现了故障。Nacos服务端会根据客户端的心跳包不断更新服务的状态。
注册原理:
Nacos提供了SDK和Open API两种形式来实现服务注册。API SDK。
这两种形式本质都一样,底层都是基于HTTP协议完成请求的。所以注册服务就是发送一个HTTP请求。
总结:Nacos客户端通过Open API的形式发送服务注册请求,Nacos服务端收到请求后,做以下三件事:
- 构建一个Service对象保存到ConcurrentHashMap集合中
- 使用定时任务对当前服务下的所有实例建立心跳检测机制
- 基于数据一致性协议服务数据进行同步

客户端订阅步骤:


第一步:订阅方法的调用,并进行EventListener的注册,后面UpdateTask要用来进行判断;
第二步:通过委托代理类来处理订阅逻辑,此处与获取实例列表方法使用了同一个方法;
第三步:通过定时任务执行UpdateTask方法,默认执行间隔为6秒,当发生异常时会延长,但不超过1分钟;
第四步:UpdateTask方法中会比较本地是否存在缓存,缓存是否过期。当不存在或过期时,查询注册中心,获取最新实例,更新最后获取时间,处理ServiceInfo。
第五步:重新计算定时任务时间,循环执行上述流程。
客户端订阅流程如图:

UpdateTask,run运行源码:
public void run() {
long delayTime = DEFAULT_DELAY;
try {
if (!changeNotifier.isSubscribed(groupName, serviceName, clusters) && !futureMap.containsKey(
serviceKey)) {
NAMING_LOGGER.info("update task is stopped, service:{}, clusters:{}", groupedServiceName, clusters);
isCancel = true;
return;
}
// 获取缓存的service信息
ServiceInfo serviceObj = serviceInfoHolder.getServiceInfoMap().get(serviceKey);
if (serviceObj == null) {
// 根据serviceName从注册中心服务端获取Service信息
serviceObj = namingClientProxy.queryInstancesOfService(serviceName, groupName, clusters, 0, false);
serviceInfoHolder.processServiceInfo(serviceObj);
lastRefTime = serviceObj.getLastRefTime();
return;
}
// 过期服务(服务的最新更新时间小于等于缓存刷新时间),从注册中心重新查询
if (serviceObj.getLastRefTime() <= lastRefTime) {
serviceObj = namingClientProxy.queryInstancesOfService(serviceName, groupName, clusters, 0, false);
serviceInfoHolder.processServiceInfo(serviceObj);
}
lastRefTime = serviceObj.getLastRefTime();
if (CollectionUtils.isEmpty(serviceObj.getHosts())) {
incFailCount();
return;
}
// 下次更新缓存时间设置,默认为6秒
// TODO multiple time can be configured.
delayTime = serviceObj.getCacheMillis() * DEFAULT_UPDATE_CACHE_TIME_MULTIPLE;
resetFailCount();
} catch (Throwable e) {
incFailCount();
NAMING_LOGGER.warn("[NA] failed to update serviceName: {}", groupedServiceName, e);
} finally {
// 下次调度刷新时间,下次执行的时间与failCount有关
// failCount=0,则下次调度时间为6秒,12,24,48...最长为1分钟
// 即当无异常情况下缓存实例的刷新时间是6秒
if (!isCancel) {
executor.schedule(this, Math.min(delayTime << failCount, DEFAULT_DELAY * 60),
TimeUnit.MILLISECONDS);
}
}
}


nacos系列参照:
https://developer.aliyun.com/profile/expert/j6mnecshi5ww2

浙公网安备 33010602011771号