系列文章目录和关于我

一丶基本概念&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 定义了诸多规范接口,无论是服务注册,还是负载均衡,让其他中间件实现
  • NacosServiceRegistry nacos服务注册接口,实现了ServiceRegistry,定义了如何注册,如何取消注册,维护服务状态等。

1.2.NacosRegistration

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

1.3.NacosAutoServiceRegistration

  • AutoServiceRegistration一个标记接口,表示当前类是一个自动服务注册类
  • AbstractAutoServiceRegistration 实现了ApplicationListener,监听WebServerInitializedEvent web服务初始化结束事件,在ApplicationListener#onApplicationEvent中进行服务注册
  • NacosAutoServiceRegistration使用NacosServiceRegistryNacosRegistration的注册到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 服务注册

可以看到这里使用NamingServiceInstance进行注册

  • 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命名中间idserviceName服务名称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服务注册源码分析的更多相关文章

  1. nacos服务注册源码解析

    1.客户端使用 compile 'com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery:2.2.3.RELEASE' compi ...

  2. Qt Creator 源码学习笔记04,多插件实现原理分析

    阅读本文大概需要 8 分钟 插件听上去很高大上,实际上就是一个个动态库,动态库在不同平台下后缀名不一样,比如在 Windows下以.dll结尾,Linux 下以.so结尾 开发插件其实就是开发一个动态 ...

  3. 微服务框架SpringCloud(Dalston版)学习 (一):Eureka服务注册与发现

    eureka-server eureka服务端,提供服务的注册与发现,类似于zookeeper 新建spring-boot工程,pom依赖: <dependency> <groupI ...

  4. dubbo学习笔记一(服务注册)

    相关的资料 官方文档 官方博客 项目结构 项目说明 [lesson1-config-api] 是一个接口工程,编译后是jar包,被其他工程依赖 [lesson1-config-2-properties ...

  5. Nacos服务注册原理分析

    在分布式服务中,原来的单体服务会被拆分成一个个微服务,服务注册实例到注册中心,服务消费者通过注册中心获取实例列表,直接请求调用服务. 服务是如何注册到注册中心,服务如果挂了,服务是如何检测?带着这些问 ...

  6. Underscore.js 源码学习笔记(上)

    版本 Underscore.js 1.9.1 一共 1693 行.注释我就删了,太长了… 整体是一个 (function() {...}());  这样的东西,我们应该知道这是一个 IIFE(立即执行 ...

  7. Hadoop源码学习笔记(6)——从ls命令一路解剖

    Hadoop源码学习笔记(6) ——从ls命令一路解剖 Hadoop几个模块的程序我们大致有了点了解,现在我们得细看一下这个程序是如何处理命令的. 我们就从原头开始,然后一步步追查. 我们先选中ls命 ...

  8. Hadoop源码学习笔记(5) ——回顾DataNode和NameNode的类结构

    Hadoop源码学习笔记(5) ——回顾DataNode和NameNode的类结构 之前我们简要的看过了DataNode的main函数以及整个类的大至,现在结合前面我们研究的线程和RPC,则可以进一步 ...

  9. Hadoop源码学习笔记(4) ——Socket到RPC调用

    Hadoop源码学习笔记(4) ——Socket到RPC调用 Hadoop是一个分布式程序,分布在多台机器上运行,事必会涉及到网络编程.那这里如何让网络编程变得简单.透明的呢? 网络编程中,首先我们要 ...

  10. 微服务架构 | *3.5 Nacos 服务注册与发现的源码分析

    目录 前言 1. 客户端注册进 Nacos 注册中心(客户端视角) 1.1 Spring Cloud 提供的规范标准 1.2 Nacos 的自动配置类 1.3 监听服务初始化事件 AbstractAu ...

随机推荐

  1. PTA1003 我要通过! (20 分)

    PTA1003 我要通过! (20 分) "答案正确"是自动判题系统给出的最令人欢喜的回复.本题属于 PAT 的"答案正确"大派送 -- 只要读入的字符串满足下 ...

  2. mkcert 生成本地SSL证书 IIS 安装

    下载mkcert https://github.com/FiloSottile/mkcert/releases/latest 管理员身份 cmd 命令目录下 mkcert-v1.4.4-windows ...

  3. CC2020 分享信息

    CC2020是鄙人第一次参与的国际计算教育报告.CC2020报告的特色是希望面向未来的教育能走近每一个教育的利益相关者,包括家长.学生.行业雇主.政府决策制定者和学术界人士.敬请各位从自己的身份角度对 ...

  4. c++ProgrammingConcept

    本文做为总章简单介绍自己的c++学习过程(学习书籍:c++编程思想) 第三章:c++中的c(part1) 第三章:c++中的c(part2)

  5. 基于predis高并发情况下实现频率控制的函数

    /** * 频率控制函数 * @param string $product 保持唯一 * @param string $key 限制频率的维度 比如uid * @param int $millisec ...

  6. 量化交易-可视化展示(grafana)

    先上图 简单的实现了一下,效果还好,可玩性强 大概部署mysql+grafana step 1: 服务器:阿里云,ucloud啥的随意,配置也不需要什么,我的是阿里云1核1GB,足以 我用的ubunt ...

  7. java 循环删除数据写法

    import java.util.ArrayList;import java.util.ConcurrentModificationException;import java.util.List;// ...

  8. 单向链表&有关类和对象

    // Test515.cpp: 定义控制台应用程序的入口点.// #include "stdafx.h"#include <iostream>using namespa ...

  9. windows系统下使用java语言,在mysql数据库中做定时数据备份、删除

    有这样一个业务需求,需要将数据归档的表每月定时备份,并且删除之前表中的数据,话不多说,直接上代码! 注意:这种方法适合数据量小,业务要求不高的场景! 项目采用SpringBoot  + MyBatis ...

  10. final修饰的作用

    在Java中,final关键字可以用来修饰类.方法和变量(包括成员变量和局部变量) 1.final修饰类 当用final修饰一个类时,表明这个类不能被继承. final类中的成员变量可以根据需要设为f ...