SpringCloud源码学习笔记3——Nacos服务注册源码分析
一丶基本概念&Nacos架构
1.为什么需要注册中心
实现服务治理、服务动态扩容,以及调用时能有负载均衡的效果。
如果我们将服务提供方的ip地址配置在服务消费方的配置文件中,当服务提供方实例上线下线,消费方都需要重启服务,导致二者耦合度过高。注册中心就是在二者之间加一层,实现解耦合。

健康检查和服务摘除:主动的检查服务健康情况,对于宕机的服务将其摘除服务列表
2.Nacos 的架构

Naming Service:注册中心,提供服务注册,注销,管理Config Service:配置中心,Nacos 配置中心为服务配置提供了编辑、存储、分发、变更管理、历史版本管理等功能,并且支持在实例运行中,更改配置。OpenAPI:nacos对外暴露的接口,Provider App(服务提供者)就是调用这里的接口,实现将自己注册到nacos,Consumer App(服务消费者)也是使用这里的接口拉去配置中心中的服务提供者的信息。
3.nacos数据模型

二丶nacos注册中心简单使用
我们使用nacos作为注册中心,只需要下载nacos提供的jar包并运行启动nacos服务,然后在服务提供者,消费者中引入spring-cloud-starter-alibaba-nacos-discovery,并配置spring.cloud.nacos.discovery.server-addr=nacos服务启动的地址,即可在nacos可视化界面看到:

那么服务是如何注册到nacos的昵?
三丶服务注册源码分析
当我们服务引入spring-cloud-starter-alibaba-nacos-discovery,便可以实现自动进行注册,这是因为在spring.facotries中自动装配了NacosServiceRegistryAutoConfiguration
SpringBoot源码学习1——SpringBoot自动装配源码解析+Spring如何处理配置类的

1.NacosServiceRegistryAutoConfiguration 引入了哪些类
点进NacosServiceRegistryAutoConfiguration 源码中,发现它注入了一下三个类
1.1.NacosServiceRegistry


ServiceInstance表示的是服务发现中的一个实例这个接口定义了类似于
getHost,getIp获取注册实例host,ip等方法,是springcloud定义的规范接口Registration一个标记接口,ServiceRegistry<R>这里面的R泛型就是Registration是springcloud定义的规范接口
ServiceRegistry服务注册,定义如何向注册中心进行注册,和取消注册这个接口定义了
register服务注册,deregister服务取消注册等方法,入参是Registration。它是springcloud定义的规范接口。
spring cloud 定义了诸多规范接口,无论是服务注册,还是负载均衡,让其他中间件实现
NacosServiceRegistrynacos服务注册接口,实现了ServiceRegistry,定义了如何注册,如何取消注册,维护服务状态等。
1.2.NacosRegistration


NacosRegistration 是 Registration的实现类,象征着一个Nacos注册中心的服务,也就是我们自己写的springboot服务
1.3.NacosAutoServiceRegistration


AutoServiceRegistration一个标记接口,表示当前类是一个自动服务注册类AbstractAutoServiceRegistration实现了ApplicationListener,监听WebServerInitializedEvent web服务初始化结束事件,在ApplicationListener#onApplicationEvent中进行服务注册NacosAutoServiceRegistration使用NacosServiceRegistry将NacosRegistration的注册到nacos注册中心
一通分析之后,可以看到NacosAutoServiceRegistration 是最核心的类,它负责监听事件,调用NacosServiceRegistry,将服务注册到注册中心。
2.AbstractAutoServiceRegistration 监听事件进行注册
此类是SpringCloud提供的模板类,让市面上众多注册中心中间件实现它,快速接入SpringCloud生态。

2.1 WebServerInitializedEvent 从何而来
AbstractAutoServiceRegistration想响应WebServerInitializedEvent ,那么WebServerInitializedEvent 是哪儿发出的昵?
在WebServerStartStopLifecycle#start方法


WebServerStartStopLifecycle实现了Lifecycle,在spring容器刷新结束的时候,会使用LifecycleProcessor调用所以Lifecycle#start,从而发送ServletWebServerInitializedEvent(WebServerInitializedEvent子类)推送事件
Reactive的springboot上下文则是由WebServerStartStopLifecycle推送ReactiveWebServerInitializedEvent事件,原理一样,如下图

2.2 NacosAutoServiceRegistration如何进行服务注册
AbstractAutoServiceRegistration在响应事件后,会调用bind方法,进而调用register进行服务注册,这里就会调用到NacosAutoServiceRegistration#register

那么到底如何进行服务注册?

可以看到直接调用NacosServiceRegistry#register(NacosRegistration)进行服务注册
3.NacosServiceRegistry 服务注册

可以看到这里使用NamingService将Instance进行注册
NamingService,nacos框架中的类,负责服务注册和取消注册Instance,nacos框架中的类,定义一个服务,记录ip,端口等信息
可以看到nacos有自己一套东西,脱离springcloud,也可以使用,这就是松耦合
下面我们看下NamingService是如何进行服务注册的
如果是临时实例,会使用
ScheduledThreadPoolExecutor,每5秒发送一次心跳,发送心跳即请求nacos注册中心/instance/beat接口然后调用
NamingProxy进行服务注册
最终底层通过Http请求的方式,请求nacos服务的
/nacos/v1/ns/instance。

4.nacos注册中心如何处理服务注册的请求
上面一通分析,我们直到了springboot服务是如何启动的时候,自动进行服务注册的,如何进行服务注册的,但是nacos服务端是如何响应注册请求的的昵

从请求中拿实例信息

主要包含上述这些字段。
ServiceManager#registerInstance服务注册的逻辑主要在
addInstance方法中
首先根据待注册服务的
namespaceId命名中间id,serviceName服务名称,ephemeral是否临时服务构建出一个key,由于我们是一个临时实例,key最终为com.alibaba.nacos.naming.iplist.ephemeral + namespaceId ## + serviceName然后调用
ConsistencyService一致性协议服务#put进行注册,这里和Nacos支持AP,CP架构有关,后续我们分析到一致性协议再补充。这里会调用到
DelegateConsistencyServiceImpl(一致性协议门面)他会根据key中的是临时实例,还是非临时实例,选择协议,最终选择到DistroConsistencyServiceImpl,继续调用put方法

可以看到
DistroConsistencyServiceImpl(Distro一致性协议服务)会同步到nacos集群中的其他实例,这部分我们后续分析,我们重点看下onPut,看看nacos服务到底如何注册。

至此服务注册请求结束了,只是将注册请求信息包装成了任务加入到
Notifier的任务队列中。
5.nacos 服务注册表结构
在看怎么处理阻塞队列中的任务前,我们看下nacos的注册表结构
6.nacos注册表结构

对应ServiceManager中的serviceMap属性
/**
* key 是命名空间
* value 是 分组名称和Service服务的map
*
*/
private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();
//Service结构如下
//集群和集群对象组成map
private Map<String, Cluster> clusterMap = new HashMap<>();
//Cluster 中的属性记录所有实例Instance的集合
6.nacos服务注册异步任务队列处理注册任务
上面分析到最终服务注册请求被包装放到Notifier的任务队列中。我们看下任务队列的任务在哪里被拿出来消费。
Notifier实现了Runnable,在DistroConsistencyServiceImpl中使用@PostConstruct将它提交到了调度线程池中。

也就是说会有一个单线程调用Notifier#run


后续会调用到Service#onChange,其updateIPs方法会更新实例的ip地址
// 这里 instances 里面就包含了新实例对象
// ephemeral 为 ture,临时实例
public void updateIPs(Collection<Instance> instances, boolean ephemeral) {
// clusterMap 对应集群的Map
Map<String, List<Instance>> ipMap = new HashMap<>(clusterMap.size());
// 把集群名字都放入到ipMap里面,value是一个空的ArrayList
for (String clusterName : clusterMap.keySet()) {
ipMap.put(clusterName, new ArrayList<>());
}
// 遍历全部的Instance,这个List<Instance> 包含了之前已经注册过的实例,和新注册的实例对象
// 这里的主要作用就是把相同集群下的 instance 进行分类
for (Instance instance : instances) {
try {
// 判断客户端传过来的是 Instance 中,是否有设置 ClusterName
if (StringUtils.isEmpty(instance.getClusterName())) {
// 如果没有,就给ClusterName赋值为 DEFAULT
instance.setClusterName(UtilsAndCommons.DEFAULT_CLUSTER_NAME);
}
// 判断之前是否存在对应的 ClusterName,如果没有则需要创建新的 Cluster 对象
if (!clusterMap.containsKey(instance.getClusterName())) {
// 创建新的集群对象
Cluster cluster = new Cluster(instance.getClusterName(), this);
cluster.init();
// 放入到集群 clusterMap 当中
getClusterMap().put(instance.getClusterName(), cluster);
}
// 通过集群名字,从 ipMap 里面取
List<Instance> clusterIPs = ipMap.get(instance.getClusterName());
// 只有是新创建集群名字,这里才会为空,之前老的集群名字,在方法一开始里面都 value 赋值了 new ArrayList对象
if (clusterIPs == null) {
clusterIPs = new LinkedList<>();
ipMap.put(instance.getClusterName(), clusterIPs);
}
// 把对应集群下的instance,添加进去
clusterIPs.add(instance);
} catch (Exception e) {
}
}
// 分好类之后,针对每一个 ClusterName ,写入到注册表中
for (Map.Entry<String, List<Instance>> entry : ipMap.entrySet()) {
// entryIPs 已经是根据ClusterName分好组的实例列表
List<Instance> entryIPs = entry.getValue();
// 对每一个 Cluster 对象修改注册表 ->updateIps
clusterMap.get(entry.getKey()).updateIps(entryIPs, ephemeral);
}
}
针对每一个集群分别进行Cluster#updateIps
public void updateIps(List<Instance> ips, boolean ephemeral) {
// 先判断是否是临时实例
// ephemeralInstances 临时实例
// persistentInstances 持久化实例
// 把对应数据先拿出来,放入到 新创建的 toUpdateInstances 集合中
Set<Instance> toUpdateInstances = ephemeral ? ephemeralInstances : persistentInstances;
// 先把老的实例列表复制一份 , 先复制一份新的
//写时复制,先复制一份
HashMap<String, Instance> oldIpMap = new HashMap<>(toUpdateInstances.size());
for (Instance ip : toUpdateInstances) {
oldIpMap.put(ip.getDatumKey(), ip);
}
//省略了同步到其他nacos服务的代码。。。
// 最后把传入进来的实例列表,重新初始化一个 HaseSet,赋值给toUpdateInstances
toUpdateInstances = new HashSet<>(ips);
// 判断是否是临时实例
if (ephemeral) {
// 直接把之前的实例列表替换成新的
ephemeralInstances = toUpdateInstances;
} else {
persistentInstances = toUpdateInstances;
}
}

SpringCloud源码学习笔记3——Nacos服务注册源码分析的更多相关文章
- nacos服务注册源码解析
1.客户端使用 compile 'com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery:2.2.3.RELEASE' compi ...
- Qt Creator 源码学习笔记04,多插件实现原理分析
阅读本文大概需要 8 分钟 插件听上去很高大上,实际上就是一个个动态库,动态库在不同平台下后缀名不一样,比如在 Windows下以.dll结尾,Linux 下以.so结尾 开发插件其实就是开发一个动态 ...
- 微服务框架SpringCloud(Dalston版)学习 (一):Eureka服务注册与发现
eureka-server eureka服务端,提供服务的注册与发现,类似于zookeeper 新建spring-boot工程,pom依赖: <dependency> <groupI ...
- dubbo学习笔记一(服务注册)
相关的资料 官方文档 官方博客 项目结构 项目说明 [lesson1-config-api] 是一个接口工程,编译后是jar包,被其他工程依赖 [lesson1-config-2-properties ...
- Nacos服务注册原理分析
在分布式服务中,原来的单体服务会被拆分成一个个微服务,服务注册实例到注册中心,服务消费者通过注册中心获取实例列表,直接请求调用服务. 服务是如何注册到注册中心,服务如果挂了,服务是如何检测?带着这些问 ...
- Underscore.js 源码学习笔记(上)
版本 Underscore.js 1.9.1 一共 1693 行.注释我就删了,太长了… 整体是一个 (function() {...}()); 这样的东西,我们应该知道这是一个 IIFE(立即执行 ...
- Hadoop源码学习笔记(6)——从ls命令一路解剖
Hadoop源码学习笔记(6) ——从ls命令一路解剖 Hadoop几个模块的程序我们大致有了点了解,现在我们得细看一下这个程序是如何处理命令的. 我们就从原头开始,然后一步步追查. 我们先选中ls命 ...
- Hadoop源码学习笔记(5) ——回顾DataNode和NameNode的类结构
Hadoop源码学习笔记(5) ——回顾DataNode和NameNode的类结构 之前我们简要的看过了DataNode的main函数以及整个类的大至,现在结合前面我们研究的线程和RPC,则可以进一步 ...
- Hadoop源码学习笔记(4) ——Socket到RPC调用
Hadoop源码学习笔记(4) ——Socket到RPC调用 Hadoop是一个分布式程序,分布在多台机器上运行,事必会涉及到网络编程.那这里如何让网络编程变得简单.透明的呢? 网络编程中,首先我们要 ...
- 微服务架构 | *3.5 Nacos 服务注册与发现的源码分析
目录 前言 1. 客户端注册进 Nacos 注册中心(客户端视角) 1.1 Spring Cloud 提供的规范标准 1.2 Nacos 的自动配置类 1.3 监听服务初始化事件 AbstractAu ...
随机推荐
- ansible 详解基本篇
Ansible是一种常用的自动运维化工具,基于python开发,分布式,无需客户端,轻量级,配置语言采用YAML. 安装方式yum yum install epel-release&& ...
- prometheus 配置数据保留7天时间storage.tsdb.retention.time
1.修改配置如下: 默认24h添加配置:retention: 168h
- 4vue 属性绑定
属性绑定v-bind <!DOCTYPE html> <html lang="en"> <head> <meta charset=&quo ...
- 《JavaScript高级程序设计》Chapter03 JavaScript语言基础
目录 Syntax Variable var let const Data Type Undefined Null Boolean Number String Symbol Object Operat ...
- vue引入多个指令文件
单个指令引入,在main.js(入口JS文件)中引入你已经写好的指令文件,可以省略文件后缀: // main.js import focus from 'xxx/directive'多个指令引入 Vu ...
- url not set
UrI not set 原因与处理方法 今天下午跑代码时发现,上午能跑的代码下午跑不了了.一直报 Url not set错误. 出现这个问题的主要原因,是因为代码中的@ConfigurationPro ...
- c# 连接SQLite 查询数据 写入txt文本
using Newtonsoft.Json.Linq; using System; using System.Data.SQLite; using System.IO; namespace @publ ...
- Docker部署NextCloud
docker run -d -p 80:80 nextcloud 数据库可以选Mysql或者Pg 下载客户端 https://nextcloud.com/
- CH9141进阶应用篇
在基础篇中主要将的是主从连接透传数据,这也是CH9141模块的主要功能,这边呢就主要讲讲除了透传之外的功能, 如通用GPIO,ADC采集功能,串口配置功能. 这些功能均有两种实现方式,一种是通过AT指 ...
- win10 自带输入法设置小鹤双拼
1.创建bat文件: 小鹤双拼.bat 2.编辑小鹤双拼.bat 添加内容: reg add HKEY_CURRENT_USER\Software\Microsoft\InputMethod\Sett ...