nacos注册中心

目录

nacos

概念

nacos和eureka注册中心对比 and CAP定律理解

nacos服务注册源码分析

服务注册处理

nacos注册中心处理

心跳机制

nacos客户端

nacos服务端

nacos

概念

命名空间:用于进行租户粒度的配置隔离,常用场景之一是不同环境的配置的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。

配置:需要变更的参数、变量等从代码中分离出来独立管理,以独立的配置文件的形式存在。通常以 param-key=param-value 的形式存在。

配置集:一组相关的配置项的集合称。一个配置文件通常就是一个配置集,一个系统或者应用可以包含多个配置集。常用于不同的应用或组件使用了相同的配置类型,如 database_url 配置和 MQ_topic 配置。

服务注册中心:存储服务实例和服务负载均衡策略的数据库。通过服务名可以唯一确定其指代的服务。不同的服务可以归类到同一分组。

服务发现:(通常使用服务名)对服务下的实例的地址和元数据进行探测,并以预先定义的接口提供给客户端进行查询。

元信息:Nacos配置和服务描述信息,如服务版本、权重、策略。

权重:实例级别的配置。权重为浮点数。权重越大,分配给该实例的流量越大。

健康检查:检查实例是否能提供服务,不健康的实例不会返回给客户端。

健康保护阈值:为了防止因过多实例不健康导致流量全部流向健康实例,继而造成流量压力把健健康实例压垮并形成雪崩效应,应将健康保护阈值定义为一个 0 到 1 之间的浮点数。当域名健康实例占总服务实例的比例小于该值时,无论实例是否健康,都会将这个实例返回给客户端。这样做虽然损失了一部分流量,但是保证了集群的剩余健康实例能正常工作。

这张图很重要。表述了namespace、group和service/dataId的包含关系。

 

nacos和eureka注册中心对比 and CAP定律理解

CAP原则

CAP定理,指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可得兼。一般分布式系统中,肯定是优先办证P,剩下的就是C和A的取舍。

分布式系统的CAP理论:理论首先把分布式系统中的三个特性进行了如下归纳:

一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
可用性(A):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
分区容错性(P):以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。

nacos支持CP和AP两种模式,eureka只支持AP模式。

CP模式主要保障数据一致性,支持C数据一致性和P分区容错

AP模式主要保障可用性,支持A可用性和P分区容错

 

初步结论为:使用Nacos代替Eureka,主要理由为:
相比与Eureka:
(1)Nacos具备服务优雅上下线和流量管理(API+后台管理页面),而Eureka的后台页面仅供展示,需要使用api操作上下线且不具备流量管理功能。
(2)从部署来看,Nacos整合了注册中心、配置中心功能,把原来两套集群整合成一套,简化了部署维护
(3)从长远来看,Eureka开源工作已停止,后续不再有更新和维护,而Nacos在以后的版本会支持SpringCLoud+Kubernetes的组合,填补 2 者的鸿沟,在两套体系下可以采用同一套服务发现和配置管理的解决方案,这将大大的简化使用和维护的成本。同时来说,Nacos 计划实现 Service Mesh,是未来微服务的趋势
(4)从伸缩性和扩展性来看Nacos支持跨注册中心同步,而Eureka不支持,且在伸缩扩容方面,Nacos比Eureka更优(nacos支持大数量级的集群)。
(5)Nacos具有分组隔离功能,一套Nacos集群可以支撑多项目、多环境。

nacos服务注册源码分析

我们以spring cloud 整合nocos为例子,当我们引入nacos start依赖后,查看spring.factories,通过配置的自动加载配置,实现去执行NacosServiceRegistry进行服务注册

NacosServiceRegistry实现了ServiceRegistry,这个ServiceRegistry接口是SpringCloud提供的服务注册的标准,集成到SpringCloud中实现服务注册的组件,都需要实现这个接口。

那么对于Nacos而言,该接口的实现类是NacosServiceRegistry

spring.factories主要是包含了自动装配的配置信息,如图:

在spring.factories中配置EnableAutoConfiguration的内容后,项目在启动的时候,会导入相应的自动配置类,那么也就允许对该类的相关属性进行一个自动装配。

NacosDiscoveryAutoConfiguration发现当中对三个对象进行了装载

  • NacosServiceRegistry:完成服务注册,实现ServiceRegistry,这样才能被调用,因为AbstractAutoServiceRegistration中的serviceRegistry属性就是接口ServiceRegistry类型,必须实现它
  • NacosRegistration:用来注册时存储nacos服务端的相关信息
  • NacosAutoServiceRegistration 继承spring中的AbstractAutoServiceRegistration,装载NacosAutoServiceRegistration的时候,会调用构造方法设置AbstractAutoServiceRegistration中的serviceRegistry属性为NacosServiceRegistry,AbstractAutoServiceRegistration继承ApplicationListener<WebServerInitializedEvent>,通过事件监听来发起服务注册,调用serviceRegistry属性进行注册,到时候就会调用NacosServiceRegistry.register(registration)

整个调用链路:

在这里插入图片描述

第一步:springboot在启动main方法时调用到spring的核心方法refresh
第二步: spring的refresh方法会在最后的流程中finishRefresh(),而由于是Springboot的方式启动时初始化的applictionContext是AnnotationConfigServletWebServerApplicationContext,并且该类是继承于ServletWebServerApplicationContext,ServletWebServerApplicationContext重写了finishRefresh方法,Spring在调用finishRefresh最终会调到子类的ServletWebServerApplicationContext当中的finishRefresh方法。
第三步:ServletWebServerApplicationContext在处理完父类的finishRefresh的时候会去发布一个ServletWebServerInitializedEvent事件,该事件继承于WebServerInitializedEvent,而最终nacos就是去监听该事件来进行注册的
第四步:spring在发送事件之后最终由AbstractApplicationContext将该事件交给ApplicationEventMulticaster将事件广播到适定的监听器。
第五步:AbstractApplicationContext的实现类SimpleApplicationEventMulticaster当中的multicastEvent中会去获取当前处理该事件的所有的监听器,并执行最终的事件处理

第六步:最后根据注册的事件,该事件的对应的监听器会实现onApplicationEvent方法,调到对应的实现上,AbstractAutoServiceRegistration会去监听前面所提到的WebServerInitializedEvent事件,最终会调到 AbstractAutoServiceRegistration中的onApplicationEvent方法
第七步:AbstractAutoServiceRegistration经过一系列的跳转调用最终调用到ServiceRegistry中的register方法。而ServiceRegistry.register实际上就是spring cloud为各个注册中心所制定的标准,要想使服务注册,那么须各个注册中心的客户端去实现该方法
第八步:在这里nacos的注册流程就完全的清楚了,由于使用了唯一的注册中心nacos,而恰巧nacos的注册流程是通过NacosServiceRegistry实现的,这里最终就调用到了nacos的注册流程。
第九步:nacos的注册流程已经走完了,其实还有个疑问,那么spring cloud中AbstractAutoServiceRegistration又是怎么初始话的呢,翻开spring cloud common的/META-INFO/spring.factories中配置AutoServiceRegistrationAutoConfiguration,而通过该类对AbstractAutoServiceRegistration进行了初始化。

服务注册处理

当nacos进行服务注册的时候,NacosServiceRegistry.class会调用register()方法进行服务注册,该方法中调用了namingService.registerInstance()方法进行服务注册的逻辑。

serviceId对应当前应用的application.name,instance表示服务实例信息

NacosNamingService实现了NamingService的接口;然后在namingService.registerInstance()方法中,会做两件事情:

  1. 如果当前注册的是临时节点,则构建心跳信息,通过beat反应堆来构建心跳任务
  2. 调用registerService发起服务注册

心跳机制先不看,后面在看,然后调用 NamingProxy  的注册方法进行注册,代码逻辑很简单,构建请求参数,发起请求。

然后我们直接往下走,可以看到服务注册的时候会轮询配置的注册中心的地址,进行注册,

最后通过 callServer(api, params, server, method) 发起调用,这里通过 JSK自带的 HttpURLConnection 进行发起调用。我们可以通过断点的方式来看到这里的请求参数:

期间可能会有多个 GET的请求获取服务列表,是正常的,会发现有如上的一个请求,会调用 /nacos/v1/ns/instance 这个地址。那么接下去就是Nacos Server 接受到服务端的注册请求的处理流程。

nacos注册中心处理

需要下载Nacos Server 源码,服务端提供了一个InstanceController类,在这个类中提供了服务注册相关的API,而服务端发起注册时,调用的接口是: [post]: /nacos/v1/ns/instance ,serviceName: 代表客户端的项目名称 ,namespace: nacos 的namespace。

@CanDistro
@PostMapping
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public String register(HttpServletRequest request) throws Exception {
        
        final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
        final String namespaceId = WebUtils
                .optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
        // 从请求中解析出instance实例
        final Instance instance = parseInstance(request);
        
        serviceManager.registerInstance(namespaceId, serviceName, instance);
        return "ok";
}

然后调用 ServiceManager 进行服务的注册

public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
        //创建一个空服务,在Nacos控制台服务列表展示的服务信息,实际上是初始化一个serviceMap,它是一个ConcurrentHashMap集合
        createEmptyService(namespaceId, serviceName, instance.isEphemeral());
        //从serviceMap中,根据namespaceId和serviceName得到一个服务对象
        Service service = getService(namespaceId, serviceName);
        
        if (service == null) {
            throw new NacosException(NacosException.INVALID_PARAM,
                    "service not found, namespace: " + namespaceId + ", service: " + serviceName);
        }
        //调用addInstance创建一个服务实例
        addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
}

在创建空的服务实例的时候我们发现了存储实例的map:

public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster)
            throws NacosException {
        //从serviceMap中获取服务对象
        Service service = getService(namespaceId, serviceName);
        if (service == null) {//如果为空。则初始化
      Loggers.SRV_LOG.info("creating empty service {}:{}", namespaceId, serviceName);
      service = new Service();
      service.setName(serviceName);
      service.setNamespaceId(namespaceId);
      service.setGroupName(NamingUtils.getGroupName(serviceName));
      // now validate the service. if failed, exception will be thrown
      service.setLastModifiedMillis(System.currentTimeMillis());
      service.recalculateChecksum();
      if (cluster != null) {
          cluster.setService(service);
          service.getClusterMap().put(cluster.getName(), cluster);
      }
      service.validate();
      putServiceAndInit(service);
      if (!local) {
          addOrReplaceService(service);
      }
}

在 getService 方法中我们发现了Map:

/**
* Map(namespace, Map(group::serviceName, Service)).
*/
private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();

通过注释我们可以知道,Nacos是通过不同的 namespace 来维护服务的,而每个namespace下有不同的group,不同的group下才有对应的Service ,再通过这个 serviceName 来确定服务实例。

第一次进来则会进入初始化,初始化完会调用 putServiceAndInit

private void putServiceAndInit(Service service) throws NacosException {
        putService(service);//把服务信息保存到serviceMap集合
        service.init();//建立心跳检测机制
        //实现数据一致性监听,ephemeral(标识服务是否为临时服务,默认是持久化的,也就是true)=true表示采用raft协议,false表示采用Distro
        consistencyService
                .listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), true), service);
        consistencyService
                .listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), false), service);
        Loggers.SRV_LOG.info("[NEW-SERVICE] {}", service.toJson());
    }

获取到服务以后把服务实例添加到集合中,然后基于一致性协议进行数据的同步。然后调用 addInstance

public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips)
            throws NacosException {
        // 组装key
        String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);
        // 获取刚刚组装的服务
        Service service = getService(namespaceId, serviceName);
        
        synchronized (service) {
            List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);
            
            Instances instances = new Instances();
            instances.setInstanceList(instanceList);
            // 也就是上一步实现监听的类里添加注册服务
            consistencyService.put(key, instances);
        }
    }

然后给服务注册方发送注册成功的响应。结束服务注册流程。

看下流程图

心跳机制

nacos客户端

只有NACOS服务与所注册的Instance之间才会有直接的心跳维持机制,换言之,这是一种典型的集中式管理机制

心跳机制

在client这一侧是心跳的发起源,进入NacosNamingService,可以发现,注册服务实例的时候才会构造心跳包,前面分析服务注册的时候已经看到在哪里了。

没有特殊情况,目前ephemeral都是true。BeatReactor维护了一个Map对象,记录了需要发送心跳的BeatInfo,构造了一个心跳包后,BeatReactor.addBeatInfo方法将BeatInfo放入Map中。

BeatReactor的构造函数中创建了一个ScheduledExecutorService线程操作对象,在里面执行了一个线程操作,BeatTask线程,然后在BeatTask线程中调用了sendBeat()方法,将心跳包作为参数;

BeatTask线程操作:调用sendBeat()方法

在sendBeat()方法中,通过http服务,/instance/beat,调用了服务端InstanceController.beat()方法。

nacos服务端

在服务端的心跳接口,InstanceController.beat内,会判断实例是否存在,如果不存在,会重新注册。(如网络不通导致实例在服务端被下线,或服务端重启临时实例丢失)

然后,执行service.processClientBeat(clientBeat)方法,调用一个线程任务

在该任务中,将上次的心跳时间,设置为当前时间

至此,nacos发送心跳的过程就到此结束。

这个最后的心跳时间有什么用呢,有个定时任务每5s会检查是不是太久没心跳,如果没心跳就下线掉这个实例。

调用流程:

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值