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服务端收到请求后,做以下三件事:

  1. 构建一个Service对象保存到ConcurrentHashMap集合中
  2. 使用定时任务对当前服务下的所有实例建立心跳检测机制
  3. 基于数据一致性协议服务数据进行同步

客户端订阅步骤:

第一步:订阅方法的调用,并进行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

posted @ 2022-01-10 14:11  倔强的老铁  阅读(229)  评论(0编辑  收藏  举报