分布式之Nacos2.1.0

一、简介

Spring Cloud Alibaba 新版本中Seata 1.5.2和Nacos 2.1.0 在性能和使用方面都有很大提升

二、设计注册中心

1、分布式框架的注意点:三高架构

  • 高可用

    高可用性(High Availability)通常来描述一个系统经过专门的设计,从而减少停工时间,而保持其服务的高度可用性(一直都能用)。

    解决方案:集群

  • 高并发

    高并发(High Concurrency)是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够同时并行处理很多请求。 高并发相关常用的一些指标有响应时间(Response Time),吞吐量(Throughput),每秒查询率QPS(Query Per Second),并发用户数等。

    解决方案:

    • 垂直扩展增强单机硬件性能
    • 水平扩展
  • 高性能

    高性能(High Performance)就是指程序处理速度快,所占内存少,cpu占用率低

    解决方案:

    指程序处理速度快: 这里设计我们数据存储结构、访问机制、集群同步

2、注册中心的设计

image.png

  • 服务注册
  • 注册表结构设计
  • 服务发现
  • 服务订阅
  • 服务推送
  • 健康检查
  • 集群同步:设计到数据同步,数据同步我们有哪些协议 raft 、distro、ZAB

三、Nacos作为注册中心源码分析

1、项目准备

  • 客户端项目:msbshop-parent
    注意版本的对应

    image.png

  • 服务端项目:nacos-2.1.0

    下载对应的nacos,进行编译

    mvn clean install -DskipTests -Drat.skip=true -f pom.xml
    
  • 启动源码服务时候指定参数

    image.png

2、服务启动入口

2.1 整体流程图

image.png

2.2 源码分析

通过学习Nacos1.4 我们知道了我们的注册对应的类是NacosNamingService,那我们Nacos2.1.0是不是也是一样的呢?

image.png

通过上图我们发现里面也有注册的信息,那我们打上断点看一下是否到这里,我们在对应注册方法打上断点,然后debug启动,如图 :

image.png

我们发现服务启动后他确实通过这里,所以这里是注入的入口,那他是从哪里来这里的呢?

我们想nacos是和Springboot整合,那可能使用了SpringBoot的自动装配

我们查看我们引入的Jar包spring-cloud-starter-alibaba-nacos-discovery.jar 查看里面spring.factoris文件

image.png

我们可以查看自动装配类,在这里我们可以通过名称来推断那个关于注册自动配置类,NacosServiceRegistryAutoConfiguration应该和我们注册相关,我们进入看一下,导入以下类

image.png

我们可以通过上面debug端点看一下堆栈信息,看涉及到那个类

image.png

他的默认实现类是NacosAutoServiceRegistration

NacosAutoServiceRegistration 实现了ApplicationListener接口,监听事件WebServerInitializedEvent(spring核心方法refresh的完成后广播事件)

2.3 总结

找入口的方式:自动装配类 spring.factories

事件驱动:NacosAutoServiceRegistration实现了applicationListener接口

3、实例注册

3.1 整体流程图

image.png

3.2 客户端
3.2.1 源码分析

image.png

image.png

我们的初始化代码如下:

image.png

image.png

image.png

上面是通过实例的参数ephemeral值来判断是否是grpcClientProxy还是httpClientProxy,我们在他的实例化的位置能判断ephemeral的值

我们根据堆栈信息去看找到instance实例化的位置

image.png

具体实例创建

image.png

分析NacosDiscoveryPropertiesimage.png

image.png

由此我们知道这里使用grpc客户端端和服务端通信,同时我们能得出结论:我们

image.png

grpcClientProxy的调用

image.png

image.png

image.png

3.2.2 源码总结
  • 判断变量 1、 debug 2、 全文搜索 定位赋值位置
  • 通过ephemeral的值判断是grpc通信,还是http通信,通过这我们能判断ap模式是用的grpc模式,cp模式是用http通信
  • 判断服务端处理类的方式,我们可以根据请求参数,找对应服务端的处理类(由于开源框架都是规范的,一般都是根据请求参数来命名,所以可以采用这种方式)
3.3 服务端

image.png

3.3.1源码分析

image.png

这里我们注意我们实际注册的应该是对应的实例,而不是服务,服务包括多个实例,具体上的实例才有对应的ip和port

image.png

image.png

image.png

image.png

问题:这里一个服务对应一个实例,我们知道一个服务应该对应多个实例,为什么这里对应一个实例呢? 他是怎样处理的?后面我们进行解答

对事件ClientRegisterServiceEvent的监听,我们可以通过全文搜索来看,哪里对应的处理的

image.png下面进行事件监听进行处理:

image.png

image.png

publisherIndexs就是注册表

3.3.2 源码总结
  1. 分清实例和服务的关系: 我们实际注册的是实例 ,一个服务包含多个实例
  2. 这里注册实例会注册到我们Client中,Client有个Map,key:是service value是对应的一个实例,也就是一个Client对应一个服务的具体实例
  3. 我们发送事件后来处理注册表,注册表结构是ConcurrentMap<Service, Set<String>> publisherIndexes
    里面key:是服务,value对应Client的clientId

4、注册表

4.1 注册表结构分析

下面是我们nacos2.1.0的注册表

image.png

这里是Nacos1.4x的注册表

image.png

4.2 总结

通过Nacos1.4 和Nacos2.1 版本的注册表结构会发现,1.4 比较复杂,而2.1是比较简单,这样2.1的加锁的地方要少一些,所以2.1的注册表的结构性能要好,所以说我们可以总结一下,Nacoa2.1比Nacos1.4性能好的原因:

  1. Nacos2.1 使用的是GRPC性能要好一些
  2. Nacos2.1 表结构简单,所以加锁的地方要少一些,性能更好一些

5、服务发现

5.1 客户端

image.png

5.1.1 源码分析

我们需要从客户端找到服务发现的入口,我们注册的入口类是NacosNamingService,那我们服务发现入口应该也在这里:我们看一下有两个方法:getAllInstances和selectInstances

image.png

image.png

那具体是那个方法,我们可以在每个方法里面打上断点,然后debug启动后,进行访问

image.png

image.png

发送请求

image.png

image.png

从上图可以判断他是通过selectInstances来获取数据的

我们通过堆栈信息我们能发现,他的调用路径是通过ribbon最终调用selectInstances方法。

image.png

image.png

key1: 从缓存中获取数据

image.png

这里注意,他是从缓存中获取数据,那他一定有定时任务处理这里的数据,否则他就会有脏数据的问题,带着这个问题我们继续学习。

key2:进行订阅

我们服务启动第一次一定是空的,所以我们进行订阅,当定于的clientProxy,具体是那个值呢?

image.png

我们能确定clientProxy的具体实现类是NamingClientProxyDelegate,好我们看一下具体

image.png

key1:定时任务

image.png

这里的UpdateTask一定是个Runable,所以我们看他的run方法

image.png

image.png

总结上图内容:从缓存获取,如果没有则发送请求获取,如果有则判断数据是否超时,在finally里面对应的内容是这个任务每6秒执行一次,如果失败就会扩大两倍时长,最大是一分钟

里面更新缓存中内容如下:

image.png

这里先写内存后写磁盘,那磁盘数据什么时候获取呢?

我们在ServiceInfoHolder构造方法中发现?

image.png

那我们看一下服务发现对应代码

image.png

image.png

image.png

5.1.2 源码总结
  • 服务启动时候读取磁盘中数据放到缓存
  • 读取磁盘数据 如果没有则发送请求获取数据,然后写到缓存
  • 读取磁盘数据,如果有则判断时间是否过期,过期则发送请求,写到缓存
  • 读取数据是一个定时任务每6秒执行一次,如果失败就会延长,但最大时长是1分钟

5.2 服务端

image.png

5.2.1 源码分析

从上文中我们可以分析出我们服务端处理类应该是ServiceQueryRequestHandler

image.png

image.png

image.png

从上图可知getAllInstancesFromIndex是重点,它是获取我们对应的实例,来我们重点分析一下:

image.png

1、获取对应的clientId,这一定是在我们的注册表中获取的,注册表示个Map ,key:是服务名称,value:是对应client的set ,不理解的可以参考一下我们的注册表

image.png

2、获取对应实例这里我们详细看一下

image.png

这里可以和我们注册表中内容详细看一下

5.2.2 源码总结

这里主要是从注册表中获取数据,所以理解这里需要看一下我们的注册表中,各个数据之间的关系

6、服务订阅

6.1 客户端源码

image.png

源码可以参考我们服务发现对应的源码

image.png

image.png

image.png

6.2 服务端源码

image.png

6.2.1源码分析

image.png

key3: 查询服务列表的信息中,会调用serviceStorage.getData(service) 来获取服务的实例,这个和我们服务发现的服务端源码是一样的,这里就不在重复

key4:进行订阅

image.png

key2:把订阅者和服务进行关系绑定

image.png

image.png

这里和注册相同,每个客户端对应一个Map,Map里面key:是服务 value:就是这个服务对应的实例

key3:发送订阅事件, 我们全文搜索ClientSubscribeServiceEvent 查看处理事件位置

image.png

image.png

更新订阅表

image.png

6.2.2 源码总结

我们的订阅很简单首先是获取对应订阅这个服务的实例,用于返回,然后进行订阅,订阅的信息是我们对应发起定于服务者和被订阅服务会以map形式放到我们的client,然后发送一个事件,这个时间就是更新我们的订阅注册表

image.png

7、服务变更推送

这里注意我们推送有两种一种是服务变更推送,一种是订阅推送

服务变更推送:对于一个服务来说,订阅者有好多,我们在订阅表中能看到ConcurrentMap<Service, Set<String>> subscriberIndexes,能获所有订阅者,然后进行推送

订阅者推送:此时服务已经稳定,我这里增加一个msb-order,那对于msb-stock又增加一个订阅者,此时我们应该将msb-stock对应的实例推送给具体新增的实例

7.1 订阅推送

image.png

7.2.1服务端源码分析

客户端在订阅的时候发送事件更改注册表后,会再发送一个事件,这个事件就会获取对应的服务实例,然后通知订阅者获取对应的服务的实例,这里和上面我们直接订阅返回对应数据有点重复,这里我们可以注意一下,接下来我们看一下源码。

image.png

全文搜索ServiceSubscribedEvent查看处理事件的地方

image.png

image.png

我们进入addTask方法

image.png

我们想这里先从map中获取如果有则合并,没有则放进去,那我们想一定有个地方从这里来获取这个任务来处理;

我们看一下这个类的构造方法

image.png

这里有个定时任务来处理我们看一下

image.png

我们进入处理任务

image.png

这里的processor.process的处理方法

image.png

我们分析一下上面到底是那个方法,1、我们可以通过debug 2、我们可以猜测,我们想我们处理的任务是PushDelayTask ,所以我们用它PushDelayTask来搜索一下

image.png

所以处理类应该是PushDelayTaskProcessor

image.png

PushExecuteTask是实现Runable的线程,那重点应该是他的run方法

image.png

进入对应的run方法

image.png

key1:获取订阅这个服务的客户端ID

image.png

上面是判断是否是全部推送,如果我们有指定的ClientId就不全部推送,如果没有我们就全部推送,我们这是订阅推送,所以有要推送的客户端

key2:进行任务处理

image.png

image.png

image.png

image.png

7.2.2 客户端源码分析

服务端发送请求参数是ServerRequest,那我们怎样在客户端找到对应的处理方法呢? 还是老办法,用ServerRequest在客户端进行搜索

image.png

这个ServerRequestHandler是个接口,我们找他的实现类,如下

image.png

那具体的实现类,我们推理可以知道他一定是NamingPushRequestHandler

好我们来分析一下NamingPushRequestHandler

image.png

7.2.3 总结

我们新增服务订阅在更新完注册表会发布一个事件ServiceSubscribedEvent,NamingSubscriberServiceV2Impl.会监听事件,然后把订阅到的数据进行推送过去,这期间真正的工作任务是PushExecuteTask他是一个线程,同时注意这里是订阅,所以我们推送的时候,直接推送给新增订阅者就可以

7.2 服务变更推送

服务变更推送和订阅推送是一样的只是推送的对象不同,我们订阅推送是给新增订阅者进行推送,服务变更推送是给所有订阅这个服务的推送

image.png

image.png

8、心跳机制

image.png

8.1 源码分析

我们进入ConnectionManager里面的start方法中,这个方法上有@PostConstruct,也就是构造方法执行完毕就执行这个方法

image.png

上图中我们可以知道它里面线程是每3秒执行一次:

由于里面方法比较多,这里我们可以看一些关键的点:

image.png

image.pngimage.png

image.png

key5:注销

image.png

image.png

image.png

image.png

发布事件ClientDisconnectEvent

image.png

image.png

image.png

image.png

8.2 总结

image.png

9、数据同步

image.png

9.1 源码分析

我们在实例注册的时候就应该进行集群同步

image.png

全文搜索ClientChangedEvent查看哪里处理

image.png

image.png

image.png

image.png

image.png

我们进入addTask方法

image.png

进入NacosDelayTaskExecuteEngine的构造方法里面,启动一个定时任务ProcessRunnable

image.png

查看ProcessRunnable的run方法

image.png

image.png

我们处理的任务是DistroDelayTask ,所以说我们查看具体方法如下

image.png

image.png

image.png

DistroSyncChangeTask是具体的任务我看一下他的run方法

image.png

image.png

image.png

处理集群同步

从上图中我们知道集群同步的请求是DistroDataRequest, 那我可以在服务端全文搜索

image.png

处理数据同步

image.png

image.png

image.png

image.png

新增实例

image.png

image.png

9.2 总结

这里集群同步时候,我们采用的异步处理,这里和我们服务变更推送类似

10、GRPC源码分析

image.png

10.1、客户端源码分析

我们找到客户端注册的代码:

image.png

image.png

通过他初始化实例,我们知道他他真正的调用方法

image.png

通过方法getExecuteClientProxy需要确定具体代理类 grpcClientProxy

image.png

我们查找grpcClientProxy的初始化的地方。

image.png

那NamingGrpcClientProxy应该grpc的核心逻辑

image.png

我们进入start方法:

image.png

上面方法分为两部分:

key1:是将处理的Handler放到对应的List serverRequestHandlers 那后面一定是在客户端处理请求的时候,从哪这里面拿到对应的Handler进行处理

image.png

key2: 启动这里 这里是关键,我们进来看一下:

image.png

image.png

上图是简单描述:我们可以看一下第4、5、6、7步

key4:选择一个服务

image.png

image.png

key5:连接服务

image.png

key6: 发送事件到队列, 我们可以全文搜索哪里处理这个队列

线程池里面的一个线程正在处理,如

image.png

image.png

image.png

image.png

image.png

key7:

如果前面同步连接都失败的话,我们就进行异步连接

image.png

我们全文搜索就会发现在上文中的第二个线程中会进行处理

image.png

image.png

image.png

总结:在连接成功还是连接失败后都是异步进行处理,我们可以参考这种方式

10.2、服务端源码分析

image.png

服务处理rpc的请求,那我们可以进行猜测一下服务端进行搜索RpcServer,如下

image.png

那BaseRpcServer应该是处理RPC的请求,我们看一下对应代码

image.png

通过名称我们应该知道他是启动server,那我们查看哪里调用他

image.png

通过上图我们知道,他是构造方法之后我们进行服务启动

image.png

key1: 增加拦截器 ,这里主要获取一些远端Ip+port信息

image.png

key2: 这里面重点是建立连接和处理请求

image.png

key2.1 处理请求

我们想我们Springmvc处理请求的时候,我们是根据路径判断对应的controller,这里我们的请求应该是那个具体的handler呢? 我们是根据type,这个type其实就是请求类型

如下图:获取对应type ,然后根据type获取对应的的RequestHandler

image.png

我们进入getByRequestType

image.png

image.png从上图我们知道这里是将相应handler以map的形式进行存放,那这个key我们通过debug知道他对应的值,为请求参数的的名称

那什么时候进行初始化的:

image.png

image.png

那Handler对应泛型的第一参数类型名称是什么,那我们拿InstanceRequestHandler来举例:也就是InstanceRequest

image.png

总结:我们处理实例就是用的监听器来获取对应的所有实例,然后以map处理,所有请求从这map中拿取对应的对象。

  • 12
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只经常emo的程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值