Nacos初探(2)-- 服务注册原理解析

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Mr_Errol/article/details/84938993
一、服务发现现状分析
本节内容整理自 小马哥技术周报 : https://github.com/mercyblitz/tech-weekly

1、Spring cloud 服务发现现状

目前市面服务发现的组件有:

Spring Cloud Eureka:

优点: 1)Spring Cloud 官方推荐

            2)AP模型,数据最终一致性

            3)开箱即用,具有控制台管理

缺点: 1)客户端注册服务上报所有信息,节点多的情况下,网络,服务端压力过大,且浪费内存

            2)客户端更新服务信息通过简单的轮询机制,当服务数量巨大时,服务器压力过大。

            3)集群伸缩性不强,服务端集群通过广播式的复制,增加服务器压力

            4)Eureka2.0 闭源(Spring Cloud最新版本还是使用的1.X版本的Eureka)

 

Spring Cloud Zookeeper

优点: 1)比较成熟的协调系统,dubbo,Spring cloud均可适配

            2)CP模型,ZAB算法,数据强一致性

缺点: 1)维护成本较高,客户端,session状态,网络故障等问题,会导致服务异常

            2)集群伸缩性限制,内存,GC和连接

            3)无控制台管理

 

Spring cloud Consul

优点: 1)适用于Service Mesh架构,使用于JAVA生态

            2)AP模型,Raft+Gossip算法,数据最终一致性

缺点: 1)未经大规模市场验证,无法保证可靠性

            2)Go语言编写,内部异常排查困难

 

Spring Cloud Nacos

优点: 1)开箱即用,适用于dubbo,spring cloud

            2)AP模型,数据最终一致性

            3)注册中心,配置中心二合一,提供控制台管理

            4)纯国产,久经双十一考验

缺点: 1)刚刚开源不久,社区热度不够,依然存在bug

            2)0.5.0 注册服务无鉴权认证机制,存在风险

 

       以上就是目前Spring Cloud服务发现领域的简单对比。

 

 二、Nacos 服务注册
通过源码跟踪的形式,解析服务注册的过程及原理。

源码准备:

    Nacos:https://github.com/alibaba/nacos

    Spring Cloud Alibab:https://github.com/spring-cloud-incubator/spring-cloud-alibaba

            spring-cloud-alibaba项目中有nacos服务注册的例子

IDEA启动Nacos

分析Spring Cloud服务发现其实不分注册中心是eureka还是Nacos,因为,两者都是Spring Cloud的实现,Spring Cloud已经将接口定义好,唯一的区别就是实现不一样。Spring Cloud将这些内容全部抽象到了spring-cloud-common包中。如果不了解,可以参考一下https://blog.csdn.net/hbuzhuhaoju/article/details/78915339。

1、Spring cloud是什么时候将服务注册到注册中心?

在spring-cloud-common包中有一个类org.springframework.cloud.client.serviceregistry.ServiceRegistry

这个类是Spring Cloud服务注册的总入口,看下它的方法:

一目了然,register就是注册入口,deregister就是注销入口,我们重点关注的也就是这两个方法的实现。

            查看register方法是哪里调用了:

查看org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration,一路往上查看调用路径,最后定位到这个类中的bind()方法:

方法中有个事件监听 @EventListener(WebServerInitializedEvent.class) ,说明服务注册,是在项目启动产生WebServerInitializedEvent 时,向注册中心进行注册。WebServerInitializedEvent 是如何产生的,这里不探讨,至少我们知道了spring cloud 一个服务是什么时候进行注册的。

回到ServiceRegistry接口,查看它的实现, org.springframework.cloud.alibaba.nacos.registry.NacosServiceRegistry 就是Nacos对这个接口的实现,所以,Nacos注册逻辑也从此处开始。

2、Nacos客户端源码分析

这里分析下NacosServiceRegistry是如何实现的,查看register()方法:

首先创建了一个 NamingService 实例,查看创建过程,通过 NacosDiscoveryProperties.namingServiceInstance()创建的,一个NacosDiscoveryProperties这个类,熟悉Springboot的一眼就看出来,这个类其实就是配置文件的一个对应的映射类,这个配置的就是spring.cloud.nacos.discovery.*的配置,也就是说,在application.properties文件中配置的有关spring.cloud.nacos.discovery均作为这个类的属性。这个类有以下几个属性:

/**
* nacos discovery server address
*/
private String serverAddr;

/**
* the domain name of a service, through which the server address can be dynamically
* obtained.
*/
private String endpoint;

/**
* namespace, separation registry of different environments.
*/
private String namespace;

/**
* nacos naming log file name
*/
private String logName;

/**
* service name to registry
*/
@Value("${spring.cloud.nacos.discovery.service:${spring.application.name:}}")
private String service;

/**
* weight for service instance, the larger the value, the larger the weight.
*/
private float weight = 1;

/**
* cluster name for nacos server.
*/
private String clusterName = "DEFAULT";

/**
* naming load from local cache at application start. true is load
*/
private String namingLoadCacheAtStart = "false";

/**
* extra metadata to register.
*/
private Map<String, String> metadata = new HashMap<>();

/**
* if you just want to subscribe, but don't want to register your service, set it to
* false.
*/
private boolean registerEnabled = true;

 

包括服务地址,endpoint,命名空间,服务名,集群,元数据等信息,看到方法:

说白了,nacos客户端就是将我们配置在application.properties文件中的相关配置,一起封装到Properties,再通过NocosFactory这个工厂创建NamingService。这样NamingService就能够携带这些信息。看到NamingService的子类NacosNamingService代码也是如此:

回到NacosServiceRegistery.register()方法,创建一个NacosNamingService之后,又封装了一个Instance实例,再调用NacosNamingService.registerInstance()方法进行注册,进去查看源码:

这里做了两件事情,

第一,创建了心跳信息,且加载到了心跳执行器BeatReactor中,BeatReactor 已经在NacosNamingService创建是就已经new 出来了,查看BeatReactor 的源码,可知,BeatReactor在创建时就启动了一个定时任务ScheduledExecutorService,这个定时任务,主要工作就是不停的通过http请求发送心跳数据到Nacos服务端:

并且携带了在NacosNamingService中组装的BeanInfo和dom信息。http请求地址可以通过代码跟踪进一步得知是:

/v1/ns/api/clientBeat
Nacos服务端如何处理这个接口的,后面再说,回到NacosNamingService.registerInstance()

第二、进一步调用注册方法

serverProxy.registerService(serviceName, instance);
查看源码,可知也是用过http PUT请求进行注册的,注册的路径是:

/v1/ns/instance
请求参数是:

客户端就是将一个http PUT请求发送到Nacos服务端,并且携带这些信息,同时,维护一个心跳,保持跟Nacos服务的连接与检测。至此,我们可以通过http工具模拟服务注册:

注册成功,看到Nacos控制台

3、Nacos服务端源码分析

我们已经知道了服务注册的路径,在Nacos项目中直接搜索,可以知道,接收请求的地方是com.alibaba.nacos.naming.controllers.InstanceController,最后处理逻辑是在com.alibaba.nacos.naming.web.ApiCommands.regService()方法里处理的:

这里的逻辑也比较清晰,总体思路就是:先注册domain,再注册service。就是通过一个com.alibaba.nacos.naming.core.DomainsManager 管理了一个Map,key值是domainName (断点dom值为service name), value是com.alibaba.nacos.naming.core.Domain ,注册时,使用的实现类是 com.alibaba.nacos.naming.core.VirtualClusterDomain,

private Map<String, Domain> raftDomMap = new ConcurrentHashMap<>();
注册的目的是将需要注册的服务Domain信息put到这个Map当中。这个过程中相对重要的类/接口:

com.alibaba.nacos.naming.raft.RaftListener 接口

com.alibaba.nacos.naming.core.DomainsManager  domain管理类,同时,内部有个RaftListener 的实现,上面所说put的逻辑也在这个实现当中。

com.alibaba.nacos.naming.core.VirtualClusterDomain 服务的Domain信息,也是RaftListener 的实现类

com.alibaba.nacos.naming.raft.RaftCore  维护了一个定时任务,用户注册Domain信息,并且有一个静态的Runable实现类com.alibaba.nacos.naming.raft.RaftCore.Notifier

下面是主要过程:

1)、RaftListener 维护了一个  List<RaftListener> listeners 用户存放需要处理的RaftListener,DomainsManager 中有RaftListener的内部实现类,DomainsManager 在实例化对象时,将自己add到了listeners当中,待后续处理。

2)、注册dom的逻辑最终落在com.alibaba.nacos.naming.raft.RaftCore#onPublish(com.alibaba.nacos.naming.raft.Datum, com.alibaba.nacos.naming.raft.RaftPeer)方法中,将需要注册的信息通过Notifier的方法

notifier.addTask(datum, Notifier.ApplyAction.CHANGE);
加入到定时任务中进行执行。也就是Notifier.run()方法。

3)、run()方法,循环对listeners进行处理。调用接口RaftListener.onChange()方法,比如是DomainsManager实现,则,调用DomainsManager.内部类onChange方法。

4)、DomainsManager.内部类onChange()当中对Dom信息进行update或者put,如果已经存在dom,则update,否则进行put

5)、listeners还有一个VirtualClusterDomain实现类,查看onChange()方法,主要是更新或者增加集群信息Map

Map<String, Cluster> clusterMap
以上过程只要大致的过程,其中还涉及到Raft算法的一些内容,同时源码中大量使用jdk的并发包中的内容,值得学习。

同时还发现,几天前看的版本这部分的实现比较简单,也就是将dom信息put到raftMap这段比较直接,没有做过多的处理,最新版本0.6.0是做了改动的貌似,是通过定时任务不断的执行(while(true)),是一个异步的过程,估计是考虑到服务大量并发注册导致的性能问题吧。
————————————————
版权声明:本文为CSDN博主「Errol的杂货铺」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Mr_Errol/article/details/84938993

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值