版权声明:本文为博主原创文章,遵循 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
/**
* 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
注册的目的是将需要注册的服务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
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
以上过程只要大致的过程,其中还涉及到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