nacos源码<1>

我们知道现在的微服务离不开nacos,ribbon,openFeign
只要有这三个核心组件,微服务的架构基本上就可以搭建起来了

分布式事务,网关,限流什么的都不是什么核心组件
也就是说 只要有这三块内容 你一个电商项目的单体架构就可以拆分出来,做成一个微服务架构, 做了微服务架构拆分之后
实际上你是要有一个注册中心nacos

各自的微服务启动的时候 比如说user.order 服务是需要吧自己注册上去的, 注册中心实际上是有一个注册表的,注册表中有各自服务对应的ip 以及port


在这里插入图片描述

当我们user服务调用其他服务比如说order服务可能就会用到
ribbon.以及feign 这些组件其实就是做个负载均衡

feign就是说让你的接口做的更好用一些 让你调用远程服务的时候 就像你server调用dao 层一样 让他的接口更好用一些


ribbon 会从我服务注册中心这边发现我们的微服务 说白了 就是找服务,找到对应服务之后放到我本地缓存中,
然后有一个负载均衡策略 比如说轮询,
找到一个Ip,然后再通过RestTemplate 吧请求发送过去

我们的上面三个组件实际上就干了这些事情
nacos 的源码我已经可以启动了,他的client  就是我们注册服务时候要引入的jar
nacos 会对外提供一系列的api 比如说服务注册 健康检查 服务发现
说白了 他底层就是一个web 服务对外提供了很多http接口,提供给对应客户端调用

在这里插入图片描述

我吧我这些服务客户端都启动了,导入服务注册的pom文件,这个pom实际上就是对应
服务端的Client包 
因为我此时是本地的nacos 服务端 
我们跨系统 微服务去调用~  底层用Feign客户端 的调用

在这里插入图片描述

我最原始的调用方法 比如说user--->order,直接通过(ip+端口号) 直接调用了。
但是现在如果有多个实例 比如说我有2个order服务 ,你(ip+端口号) 就不行了
因为另外一个实例你调用不到, 这个时候我们就得要用rabbon做一个负载均衡

通过这样一个技术  我就能调用到order, 我只要在restTemplate上加个注解@LoadBanance注解,就可以做到负载均衡调用多个实例了,

我们的order和user服务 启动就会注册到nacos服务端,到底是怎么注册上去的
我们都知道一个微服务一启动他就会注册,呢他是怎么注册的呢
我们加入这个pom 的spring-cloud-starter-alibaba-nacos-discovery  包
因为这是个springboot项目 底层就会加载spring.factorys 文件做一个自动装配

在这里插入图片描述

nacos 有很多功能 我们先看下他的服务注册,服务注册就是一个系统启动下来,
会把自己注册到注册中心,我们导入了这个Pom的client 依赖
我们怎么知道他这个包启动做了那些事情呢  我们会看到他的spring.factorys
看他在启动的时候会有什么bean 的实例化加载进spring的ioc容器呢  有哪些自动装配类会起到作用

在这里插入图片描述

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.alibaba.cloud.nacos.NacosDiscoveryAutoConfiguration,\
  com.alibaba.cloud.nacos.ribbon.RibbonNacosAutoConfiguration,\
  com.alibaba.cloud.nacos.endpoint.NacosDiscoveryEndpointAutoConfiguration,\
  com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientAutoConfiguration,\
  com.alibaba.cloud.nacos.discovery.configclient.NacosConfigServerAutoConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
  com.alibaba.cloud.nacos.discovery.configclient.NacosDiscoveryClientConfigServiceBootstrapConfiguration
 
我们猜名字 服务注册发现有关的  NacosDiscoveryAutoConfiguration 就是这个 说白了 就是注册中心 发现自动配置的类
这里面我们看到这个包在启动的时候 给我们注入了3个bean
NacosServiceRegistry

NacosRegistration
NacosAutoServiceRegistration  然后我们就得看这3个bean的作用了

spring 容器在启动的时候 说白了会吧这些bean 去给实例化 然后放入ioc容器中
这三个bean是干嘛用的,这3个Bean我们先看哪一个呢
碰到这些bean,我们一般会先从名字看带有Auto 比如说自动XX,
Auto开头的的一般可能就在容器启动的时候需要加载的  一般我们看的东西是Auto
开头的
这是Springboot源码代码的规范, 我3个bean 我挑选这个bean去看 NacosAutoServiceRegistration
auto 自动 ,可能我一个服务启动的时候这个Bean会给我们做很多事情
这是Springboot吧 这个命名为auto的概念,以后看Springboot相关的源码优先看Auto的服务,比如说


NacosAutoServiceRegistration---->ApplicationListener<WebServerInitializedEvent> 
NacosAutoServiceRegistration 这个类的父类实现了事件监听器,说白了我这个类在启动的时候就会监听这个WebServerInitializedEvent事件,监听到了  我就会调用我的 onApplicationEvent 方法来处理事件

这里运用了Spring的事件监听机制 ,只要实现了 ApplicationListener  这个接口容器在启动的时候就会调用  onApplicationEvent  这个方法

在这里插入图片描述

一般 if--->return 语句是可以过掉的  这都是分支逻辑  不是主线逻辑
调用逻辑大概是这样的 我们只看主线代码 不看分支代码
最后我们看到这个this.start()
start()----->register(); 方法  我们就开始了注册方法
// 拿到我们的服务名称
String serviceId = registration.getServiceId();
... 基于配置生成一个实例 Instance 
Instance instance = getNacosInstanceFromRegistration(registration);


	private Instance getNacosInstanceFromRegistration(Registration registration) {
		Instance instance = new Instance();
		instance.setIp(registration.getHost());
		instance.setPort(registration.getPort());
		instance.setWeight(nacosDiscoveryProperties.getWeight());
		instance.setClusterName(nacosDiscoveryProperties.getClusterName());
		instance.setMetadata(registration.getMetadata());
		return instance;
	}

	// 然后我们就开始注册了
	namingService.registerInstance(serviceId, instance);  
这时候可以看看我们的pom依赖关系

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

我们引入的pom 在服务注册的时候是这么调用的

在这里插入图片描述

// 接下来我们看这里  instance.isEphemeral()  这里肯定为true
ephemeral  这个代表Nacos中服务的节点是持久化的还是临时的,
nacos一般情况下都是临时实例,这样查询效率高一些
    public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
        if (instance.isEphemeral()) {
            BeatInfo beatInfo = new BeatInfo();
            // 注册的时候 就是 servicename+groupname
            beatInfo.setServiceName(NamingUtils.getGroupedName(serviceName, groupName));
            beatInfo.setIp(instance.getIp());
            beatInfo.setPort(instance.getPort());
            beatInfo.setCluster(instance.getClusterName());
            beatInfo.setWeight(instance.getWeight());
            beatInfo.setMetadata(instance.getMetadata());
            beatInfo.setScheduled(false);
            long instanceInterval = instance.getInstanceHeartBeatInterval();
            beatInfo.setPeriod(instanceInterval == 0L ? DEFAULT_HEART_BEAT_INTERVAL : instanceInterval);
            // 底层通过线程池 执行 发送心跳的接口
            this.beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);
        }

        // 注册服务接口
        this.serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);
    }
我们在看nacos源码的时候,第一我们的知道nacos 有什么功能, 最好跑一个demo
看看  他的服务注册api 以及心跳api 
实际上 底层都是调用了这些接口 nacos已经吧对应的api暴露出来了 在他的官网


在这里插入图片描述
在这里插入图片描述

我们现在的调用图就变成这样了
NacosRegistration 封装了服务信息 也就是我们的 yml东西

接下来我们来看nacos 服务端源码 看他的注册接口究竟干了怎么做到的注册

   public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
        
        createEmptyService(namespaceId, serviceName, instance.isEphemeral());
        
        Service service = getService(namespaceId, serviceName);
        
        if (service == null) {
            throw new NacosException(NacosException.INVALID_PARAM,
                    "service not found, namespace: " + namespaceId + ", service: " + serviceName);
        }
        
        addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
    }

在这里插入图片描述

  namingngserver我们服务发现 服务注册肯定是在这个包下面
  // 他只是一个内存结构 实例还是没有放进去
  createEmptyService(namespaceId, serviceName, instance.isEphemeral());
我上面也说了 nacos 是一个双层map结构  这样的
  
  /**
  * Map(namespace, Map(group::serviceName, Service)).
  */
    private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();

在这里插入图片描述

类似于这样 我们项目也是这样部署的
   createServiceIfAbsent  就是基于namespace 和serviceName 能获取到 就获取到 获取不到就creat
    

在这里插入图片描述

 这就是他的逻辑  
   service.init();  创建完毕service之后   他这个server.init的逻辑
   就是 利用线程池开一个线程 做健康检查 比如说你5s没有发心跳 的话 我就给你改状态之类的
  HealthCheckReactor.scheduleCheck(clientBeatCheckTask); 
之后拿到一个service在 基于这个service 增加一个实例节点
-----------------我们接下来继续来看 他的实例是如何加入进来的
  //看这个方法
  addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
  

在这里插入图片描述
在这里插入图片描述

主要看这里的put 

  onPut(key, value); 这里的onput方法
  主要就做了2件事情 第一件事情就是吧唯一key和所有的实例 放入这个datastore中
  第二个事情就是  基于这个key 放入阻塞队列
   dataStore.put(key, datum);
   notifier.addTask(key, DataOperation.CHANGE);
    // datumKey 操作
	tasks.offer(Pair.with(datumKey, action));
Notifier是个线程  当然我们要看他的run方法,主要看他这个handle 处理方法

在这里插入图片描述

// 看这里数据改变了 就会做些事情
    listener.onChange(datumKey, dataStore.get(datumKey).value); 

在这里插入图片描述

又回到了String key, Instances value 当初的这里了

    ///  最后又回到了 key value 了
    @Override
    public void onChange(String key, Instances value) throws Exception {

        Loggers.SRV_LOG.info("[NACOS-RAFT] datum is changed, key: {}, value: {}", key, value);
  设置了一些基础配置 权重什么的  
        for (Instance instance : value.getInstanceList()) {

            if (instance == null) {
                // Reject this abnormal instance list:
                throw new RuntimeException("got null instance " + key);
            }

            if (instance.getWeight() > 10000.0D) {
                instance.setWeight(10000.0D);
            }

            if (instance.getWeight() < 0.01D && instance.getWeight() > 0.0D) {
                instance.setWeight(0.01D);
            }
        }
//  设置均衡权重  做一些默认的设置   更改什么??
        updateIPs(value.getInstanceList(), KeyBuilder.matchEphemeralInstanceListKey(key));

        recalculateChecksum();
    }

之后看这里
 updateIPs(value.getInstanceList(), KeyBuilder.matchEphemeralInstanceListKey(key));

com.alibaba.nacos.naming.core.Service#updateIPs  这个方法中用了CopyOnWrite的思想

在这里插入图片描述

意思是他新加入的节点的时候放到的是在这个ipMap 中而此时没有操作
clusterMap 我i以后处理完毕之后 我再做替换

在这里插入图片描述

真正的替换逻辑再这里  也就是说此时才会吧我们的ipMap--------->推入到我们的临时节点ephemeralInstances 和持久节点中
他在做业务处理的时候他操作的是一个复制出来的集合 他没有操作这个内存注册表的集合  
回头他>>>做一个合并 之后再做替换
对于nacos 我可能要有很多实例 比如说我订单服务可能就会有几百个机子
也就是说时时刻刻有节点在注册,时时刻刻有节点在发现从注册表中拿出来

可能我每一秒内读写很高,现在这种思想 你写的时候新开一个副本,读的时候用你原来的,回头我写完了再跟你原先的读的呢个内存结构合并

我真正的读就在我之前的map 中,当我去写的时候就吧当前的内存注册表复制一遍,写完毕再跟真正的内存map合并,


eureka 是做了多级缓存提高我们注册中心读写并发能力 这2种架构的优缺点就是
可能我eureka注册了 我客户端可能超过1分钟才会去发现, 所以eureka 有个很大的问题就是
我服务提供者可能注册了完毕之后,我服务发现者可能 需要过几十秒才能够发现真正的服务

在这里插入图片描述

 服务发现的时效性会高的多 ,内存直接做替换, 客户端感知的时效性不如nacos
第二我们真正注册逻辑 实际上是丢到队列去异步消费去了  这样做的好处是提高并发 
对于我们操作的性能很大的提升
 在开源框架中为了性能会做一些 异步啊 队列了  copyonwrite 啊, 我的性能会提升很大的

比方说eureka的多级缓存架构~ ,copyonwrite思想 , 缓存之间的 定时任务逻辑,
异步,队列
对于注册中心怎么提升我们的高并发操作

 
outPut完毕之后 就开始做集群中间的数据同步了~ 此时也是用的线程池去处理的 走的异步

我们看一下服务端的心跳逻辑

在这里插入图片描述

/nacos/v1/ns/instance/beat  这是客户端调用服务端心跳逻辑的接口

他这里 会先找这个服务的, 找不到的话 我会重新注册
如果可以找到对应服务的话 我更改 这个服务的
instance.setLastBeat(System.currentTimeMillis());  为当前时间
如果我服务端挂了,在重启我客户端怎么重新写到服务端去了.  因为他发送心跳的时候 如果找不到的话会重新注册  
实例内部有一个lastbeat字段 ,每次心跳完毕后会更改这个字段设置为当前时间,
   

在这里插入图片描述
在这里插入图片描述

我们来看一下他这个server 放进去之后的之后逻辑   也就是说健康检查的逻辑
service.init();  主要看这里的init 方法

在这里插入图片描述

主要看这个健康检查的方法

在这里插入图片描述

如果这里当前时间大于 最后一次心跳一段时间, 我就给这个实例健康状态设置为false

在这里插入图片描述

当然时间过长的话 我就给他删除了 deleteIp  下线

我们再看下nacos 的client 怎么从服务端拉取注册表  , 他大概是这样的原理 

比如说user 调用order服务, 他会首先会从user 的本地缓存中去读取对应服务的列表,如果读取不到的话 就会去调用远程nacos 服务接口 然后将读取到的服务进行本地缓存

第二个逻辑就是 客户端这里会有一个定时任务每隔一段时间去拉取服务 目的是 我客户端我要做到动态感知你服务端的情况,比如说你此时新加入一个节点,或者down 一个节点

在这里插入图片描述

我们看下这个接口

在这里插入图片描述
在这里插入图片描述

这里就是从本地缓存去读取服务 读取不到的话就去调用远程接口 去拉服务放到本地 
 ServiceInfo serviceObj = getServiceInfo0(serviceName, clusters);
   return serviceInfoMap.get(key);  // 本地就是一个 serviceInfoMap  缓存map

在这里插入图片描述

看他此时就  从远程拉取到服务列表然后 put到这个serviceInfoMap中去了
 scheduleUpdateIfAbsent(serviceName, clusters);
 之后我们再来看一下这里的定时任务的任务

在这里插入图片描述

定时任务就是不断的去从服务端去拉取服务
我们再讲讲nacos 集群中的cap架构 nacos 是既支持ap 也支持cp的

如果你注册的节点是一个临时节点  查询块因为你是从内存中加载的 但是nacos 重启数据会丢失  此时nacos 服务端走的是因此AP模式
如果你注册的节点是一个持久化节点 服务端走的是因此CP模式   
cap架构是分布式系统数据一致性的准则

在这里插入图片描述

比如说我们有一个分布式系统,他里面有2个节点,作为分布式系统,我最终要达到数据是一致性的,也就是说我节点1新增完毕数据 之后就去做数据同步  这样才能保证我集群节点的数据一致性


现在突然出现一个问题就是我集群中的2个节点由于网络抖动出现了网络分区 说白了就是网络不通了
2个节点没有办法通信 这个叫做分区,分区就是分布式系统多个节点之间网络不通了,我要同步的数据同步不了了这个叫做分区

容错 就是我分布式系统就算是分区了,说白了 我2个节点不能相互访问了。我不能因为这个问题导致我服务down掉  这个就是容错


分区容错是针对于分布式系统来说的,就单点系统是不存在的,nacos 单机是不存在分区容错行的
 
分区容错是形容我多节点部署的集群系统 分区就是指网络原因导致的 我集群的节点之间无法通信
但是容错是 即使我集群的节点无法通信 网络不同步,我也要保证我结群的节点不能down掉
至少我还是可以访问的  
分布式系统不能因为简简单单的网络不通 你整个集群就不能对外提供服务
即使你网络不通了 你这个系统还要对外提供服务
也就是对于分布式系统来说我们肯定要满足分区容错性,分布式系统达到高可用,P是必须要满足的,如果P 不满足了意味着我集群中间出现网络问题了,整个系统就瘫痪了,就不能对外提供服务了,
分区容错说白了 就是你网络不通了, 你的服务还是要正常提供的,这个叫做分区容错性,这个也就是分布式架构中具体要有的能力,所以一般分布式系统中P是必须要满足的,
分布式系统要满足P 就意味着我网络不通了,我就没有办法来同步数据了,我节点1就没有办法吧数据同步到节点2 

C 一致性
A 可用性
如果我现在保证C 要保证整个分布式系统数据的的一致性,就意味着我这些节点都要有刚刚的呢一条数据
就得牺牲我们得可用性, 就得让我们这个分布式系统 在数据同步以前暂时不可用
保证一致性就意味着 对于我Client来说 我下次不管调用那个节点 我都应该吧这条数据查出来
保证C  对于客户端得得感觉就是 在数据同步到节点2之前 让这个分布式系统暂时不可用
如果我现在满足P的时候要满足可用性, 呢么在网络恢复之前,我就不能保证数据的一致性,因为我要保证我的高可用

P的意思是 我不能因为分区了 我整个集群就down 掉了,因为网络不同步了 我就吧我所有的节点都给他stop调
我们再看一下如果你注册的节点是一个持久化节点 服务端走的是因此CP模式   
如果他是持久化节点,呢么写注册表的逻辑就是

在这里插入图片描述

如果不是leader 他会讲这个请求转发到leader ,因为只有leader才能写数据
如果当前节点是leader的话 会通过过半投票机制来进行校验
只要认为半数节点都成功了 我就告诉客户端写成功了

如果是临时节点的话,他整个集群节点是点对点的,(各个都一样)没有说整个集群哪一个是主节点,哪一个是从节点,  当前节点通过copyonwrite写入之后就开始做集群的数据同步 利用异步
如果是持久化节点的话,就像zk一样  他这个集群有一个leader的概念,如果往集群中加入一个实例的话
如果你的请求没有发到leader节点,如果发到的是follow节点, 他需要吧这个请求转发到follow上
只有leader才可以写数据 如果当前节点是leader的话 我通过过半机制来做校验
  
过半数发 成功了 我就认为是成功了
final CountDownLatch latch = new CountDownLatch(peers.majorityCount());  
来判断他半数节点以上是否同步成功

在这里插入图片描述

只有过半机制过了之后他才会回应客户端,就类似于Raft协议

在这里插入图片描述

如果说你是持久话节点 你服务如果down  重启的话 他会从文件中恢复到内存中去了
这就是持久化和非持久化的节点  区别
如果非持久化节点 的话 挂了 数据就会丢失

当Leader挂了的话他选举的逻辑   
当leader 挂了 再写其他节点 就写不成功,内部先做Leader选举(根据过半机制)
选举完leader 才能对外提供服务 才能数据一致 说白了中间选举的时间 是不可用
不能写数据了


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值