目录
序言:
Nacos作为SpringCloud Alibaba (SCA)中注册中心以及配置中心的组件。这里主要分析Nacos客户端与服务端之间注册,下线,心跳续约源码以及对Ribbon负载均衡的Nacos的具体实现。
核心对外提供API接口类:
对于客户端的使用其实很简单,只要引入spring-cloud-starter-alibaba-nacos-discovery包即可。这个包是一个springboot start项目,所以查看它的源码在
上述的每一个AutoConfiguration都有自己需要初始化的对象例如
-
NacosDiscoveryAutoConfiguration:用于初始化NacosDiscoveryProperties(存储配置属性类),NacosServiceDiscovery(提供对nacos中service以及Instance实例访问具体实现)
-
RibbonNacosAutoConfiguration:用于初始化Ribbon中核心ServerList类,以及自己实现NacosServerIntrospector,其中Ribbon依赖组件进行初始化(IRule,IPing等)还是在RibbonAutoConfiguration中被创建
-
NacosDiscoveryEndpointAutoConfiguration:对外提供端点信息(包含Health健康检查),需要依赖于actuator项目
-
NacosServiceRegistryAutoConfiguration:这里是对核心类一些类进行初始化,例如NacosServiceRegistry(包含创建后续与nacos服务端交互的核心类NamingService-实现NacosNamingService)负责客户端的注册,下线心跳续约检测,NacosAutoServiceRegistration负责触发上述动作
1:客户端核心类:
-
NacosNamingService:client发起的后续的所有任务都是基于此类或者此类中包含的组件类来进行,NacosNamingService(该类由NamingFactory工厂类进行创建)通过读取Properties进行初始化
-
BeatReactor:客户端与服务端周期心跳检测类,内部定义ScheduledThreadPoolExecutor周期调度器,创建名为com.alibaba.nacos.naming.beat.sender线程周期的执行BeatTask任务(该类为BeatReactor中的一个内部类用于向服务端发送心跳信息,最终通过httpclient发送路径为/instance/beat的http请求),内部维护以serviceName+groupName+ip+host为key,BeatInfo为value的map,当client初始化向服务端注册实例时会创建一个BeatInfo对象,通过BeatReactor中addBeatInfo()函数写入,并在beatInfo属性period(默认5s)后调度一次BeatTask。关于心跳检测可以参考后续对心跳机制描述
-
HostReactor:客户端周期去拉取服务端代码,内部定义ScheduledThreadPoolExecutor周期调度器,创建名为com.alibaba.nacos.client.naming.updater线程周期的执行UpdateTask任务(该类为HostReactor中的一个内部类用于更新client中缓存的服务注册列表信息,在获取列表的同时,告诉服务度它的udp端口号信息,服务端生成对应的PushClient对象,一旦服务端中对应的Service信息发生来变更,服务端可以通过PushClient进行发送变更信息。UpdateTask以Service-cluster组合为单位来周期更新的,更新频率默认1s可设置。通过updateServiceNow()发送http请求-/instance/list)
-
EventDispatcher:事件分发器 用于管理EventListener。内部定义ScheduledThreadPoolExecutor周期调度器,创建名为com.alibaba.nacos.naming.client.listener线程周期的执行Notifier任务(通过Notifier向注册的EventListener中发生NamingEvent事件,可用于本地扩展(实现ApplicationListener接口监控NamingEvent事件))
-
PushReceiver:用于接受服务端发送来的ACK数据并进行与本地信息对比更新,最后返回服务端ack信息,该类初始化时创建一个udpSocket,用于与服务端数据通信,定义ScheduledThreadPoolExecutor创建前缀名为com.alibaba.nacos.naming.push.receiver的调度器,用于执行PushReceiver(该类本身实现了Runnable接口)。
//执行run源码 @Override public void run() { while (true) { try { // byte[] is initialized with 0 full filled by default //定义 byte[] buffer = new byte[UDP_MSS]; DatagramPacket packet = new DatagramPacket(buffer, buffer.length); //接受服务端发送的udp请求 udpSocket.receive(packet); //解析数据 String json = new String(IoUtils.tryDecompress(packet.getData()), "UTF-8").trim(); NAMING_LOGGER.info("received push data: " + json + " from " + packet.getAddress().toString()); //转化PushPacket PushPacket pushPacket = JacksonUtils.toObj(json, PushPacket.class); String ack; //感觉不同数据类型 返回不同ack信息 if ("dom".equals(pushPacket.type) || "service".equals(pushPacket.type)) { //处理请求 更新本地本地Service信息 hostReactor.processServiceJSON(pushPacket.data); // send ack to server ack = "{\"type\": \"push-ack\"" + ", \"lastRefTime\":\"" + pushPacket.lastRefTime + "\", \"data\":" + "\"\"}"; } else if ("dump".equals(pushPacket.type)) { // dump data to server ack = "{\"type\": \"dump-ack\"" + ", \"lastRefTime\": \"" + pushPacket.lastRefTime + "\", \"data\":" + "\"" + StringUtils.escapeJavaScript(JacksonUtils.toJson(hostReactor.getServiceInfoMap())) + "\"}"; } else { // do nothing send ack only ack = "{\"type\": \"unknown-ack\"" + ", \"lastRefTime\":\"" + pushPacket.lastRefTime + "\", \"data\":" + "\"\"}"; } //重新发送给原服务 udpSocket.send(new DatagramPacket(ack.getBytes(Charset.forName("UTF-8")), ack.getBytes(Charset.forName("UTF-8")).length, packet.getSocketAddress())); } catch (Exception e) { NAMING_LOGGER.error("[NA] error while receiving push data", e); } } }
服务端通过InstanceController接受到/instance/list路径请求,做了什么事情呢?接受client请求数据,获取存在的Service信息(可能存在访问的服务器上还未同步到Service信息未null,若出现直接返回),如果携带中参数包含udp port并且服务端开启了push机制(默认开启但是可以通过配置关闭),通过PushService中addClient()函数创建该请求与Service连接对象(PushClient信息,),并存储在key为namespaceId, serviceName构建,value为PushClient的缓存中(后续通过心跳保持该PuchClient连接),用于后续通知。组装最新Service信息返回(中间需要对Service进行校验)。
2:服务端核心代码:
2.1:数据模型有关
-
Instance:nacos数据模型中最小存储单位,主要由ip+port来确定唯一性,一个client对应一个instance
-
Cluster:集群由相同的配置的Instance构成,内部存储了该集群下persistentInstances与ephemeralInstances。Cluster实例完成之后通过HealthCheckTask来检测所属Instance活性
-
Service:代表一个服务实例,由多个Cluster构,实现了RecordListener<Instances>接口,当服务下的实例集群发生变化(增删该,通过事件驱动来解耦),触发其onChange事件,更新本服务器实例信息之后,同时通过PushService来推送注册到其实例下的client变更的节点信息
-
namespace:命名空间,可以使用它来管理一个注册中心管理多个环境(开发,测试(不同环境),生产)等
2.2:数据一致性有关
Nacos中提供的两种一致性算法的实现:CP的Raft与AP的distro,这两种算法分别针对于临时节点与持久性节点的存储,nacos中默认节点类型为临时节点。
nacos中通过DelegateConsistencyServiceImpl类(静态代理设计模式,包含两种具体策略实现),代理执行具体的数据(按照节点的类型)写入
具体算法实现: