Nacos总结
角色说明
Nacos Server:Nacos服务提供者,里面包含的Open API是功能访问入口,Conig Service、Naming Service 是Nacos提供的配置服务、命名服务模块。Consitency Protocol是一致性协议,用来实现Nacos集群节点的数据同步,这里使用的是Raft算法。
Provider APP:服务提供者
Consumer APP:服务消费者
注册中心的原理
① 服务实例在启动时注册到服务注册表,并在关闭时注销
② 服务消费者查询服务注册表,获得可用实例
③ 服务注册中心需要调用服务实例的健康检查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