当前位置: 首页 > 科技观察

16图-Nacos架构原理①:一个注册请求会经过什么?

时间:2023-03-14 16:33:19 科技观察

这次来聊聊Nacos注册服务的底层原理。Nacos作为注册中心,接收客户端(服务实例)发起的注册请求,并将注册信息存储在注册中心进行管理。那么注册请求会经过哪些步骤呢?知识点预览首先呈现一个整体流程图:集群环境:如果是Nacos集群环境,拓扑结构是怎样的?组装请求:客户端组装好注册请求,接下来就是向Nacos服务发起远程调用。随机节点:客户端在集群中随机选择一个Nacos节点发起注册,实现负载均衡。路由转发:Nacos节点收到注册请求后,检查是否属于自己。如果没有,则执行路由和转发。处理请求:节点转发到指定节点后,解析出注册请求中的实例信息,存储到自定义的内存结构中。最终一致性:Nacos自研Distro协议执行延时异步任务,同步注册信息到集群其他节点,保证数据的最终一致性。异步重试:如果注册失败,客户端会切换Nacos节点,重新发起注册请求,保证高可用。这些知识点还有很多细节,我会通过画图+源码分析的方式给大家解答。如果遇到看不懂源码的,可以看看我画的图,然后把源码翻过来,一起对比一下。小提示:本文使用的Nacos版本:2.0.4。一、来源:发起注册1.1源码阅读小贴士在上一篇文章中,我们提到添加注解@EnableDiscoveryClient可以让服务自动注册到Nacos。那么发起注册的地方在哪里呢?注册信息是什么样子的?让我告诉你一个查看源代码的小技巧。拿到源码后,与其每个文件直接看一篇文章,不如先看源码。带有示例文件夹。如下图,找到example的App类,里面有发起注册的示例代码。如下图所示:当然我们也可以通过官网给出的curl命令发起HTTP请求:curl-XPOST'http://127.0.0.1:8848/nacos/v1/ns/instance?serviceName=nacos.naming.serviceName&ip=20.18.7.11&port=8080'留个问题:我们都添加一个Nacos注解@EnableDiscoveryClient,服务实例会自动注册到Nacos中。这是怎么做到的?1.2发起注册流程图看一下代码Flowchart:按照这个流程图,我们debug看看。1.3拼装注册实例信息入口核心代码如下图所示。它将注册的实例信息组装起来放到一个实例变量中:通过代码调试,我们可以看到里面的实例信息是这样的:1.4组装注册请求request发起注册的核心方法是doRegisterService()。组装后的请求如下图所示,其中包含了之前组装好的实例信息instance,以及指定的namespace(Nacos命名空间)、serviceName(服务名称)、groupName(Nacos分组)。发起注册源码1.5发起远程调用requestToServer()方法会调用RpcClient的request()方法:response=this.currentConnection.request(request,timeoutMills);就是向Nacos发起远程调用,如果是Nacos集群,就是向集群中的一个Nacos节点发起远程调用。接下来我们看看客户端是如何选择一个Nacos节点进行注册的。2、集群环境:如果分布式前提是Nacos集群环境,客户端会随机选择一个Nacos节点发起注册。2.1搭建Nacos集群环境为了说明客户端如何注册到Nacos集群环境的底层原理,我在本地搭建了一个Nacos集群环境。有3个Nacos服务,IP相同,端口号不同。192.168.10.197:8848192.168.10.197:8858192.168.10.197:8868集群环境和服务A、服务B配置Nacos集群的IP和端口号,配置如下:spring.cloud.nacos.discovery.server-addr=192.168.10.197:8848,192.168.10.197:8858,192.168.10.197:8868整体结构如下图所示,服务A和服务B都注册到了Nacos集群。服务A和B都注册到了集群,但是有一个问题:服务A注册的时候,是不是应该注册到所有的Nacos节点上?还是只向其中一个节点发起注册?如果它只向一个节点注册,应该向哪个节点注册?答:在Client发起注册前,后台线程会随机获取Nacos集群服务列表中的一个地址。Nacos为什么要这样设计?这里面其实是一个负载均衡的思想,每个节点平均分配请求。为保证高可用,当一个节点宕机时,获取另一个Nacos节点建立连接。接下来我们看看服务A是如何随机获取一个Nacos节点的。3.随机节点:一个平等的世界让我们看看客户端如何随机选择一个节点。流程图如下:那么如何找到这些代码逻辑呢?什么想法?前面我们说过,RpcClient会发起一个请求,使用与Nacos的currentConnection连接来发起调用,代码如下://发起调用response=this.currentConnection.request(request,timeoutMills);这个currentConnection是客户端和Nacos集群中某个节点建立的连接,我们来看看它分配到哪里了。代码如下://获取Nacos节点信息serverInfo=recommendServer.get()==null?nextRpcServer():recommendServer.get();//连接Nacos节点connectToServer=connectToServer(serverInfo);//分配currentConnectionthis.currentConnection=connectToServer;而连接信息是通过参数serverInfo传入的,那么我们看一下serverInfo赋值在哪里。这个nextRpcServer()方法会得到一个随机的Nacos地址://一个int随机数,范围[0~Nacos的数量)currentIndex.set(newRandom().nextInt(serverList.size()));//indexself-increment1intindex=currentIndex.incrementAndGet()%getServerList().size();//返回Nacos地址returngetServerList().get(index);总结:客户端生成一个随机数,然后通过这个随机数从Nacos服务列表中获取一个Nacos服务地址返回给客户端,然后客户端通过这个地址与Nacos服务建立连接。Nacos服务列表中的节点都是平等的,任意一个随机获取的节点都可以用来发起调用。4.路由转发:不是我的菜4.1发起和转发请求的过程为了演示发起注册的过程,我这里模拟了一个注册请求。使用curl命令向Nacos节点(127.0.0.1:8848)发起注册请求:curl-XPOST'http://127.0.0.1:8848/nacos/v1/ns/instance?serviceName=nacos.naming。serviceName&ip=20.18.7.11&port=8080'RequestURL:/nacos/v1/ns/instanceRequestparameter:serviceName=nacos.naming.serviceNameip=20.18.7.11port=8080'前面我们提到Nacos有多个节点可以处理请求,当节点发现请求不属于自己时,会转发。如下图所示:服务A随机选择一个Nacos节点(图中的Nacos1)发起注册请求。请求参数包含实例信息。Nacos1根据实例信息hash+取模得到正确的节点。如果不属于自己,则将请求转发给其他节点(图中的Nacos2)。那么路由转发的细节是怎样的呢?这就涉及到Distro协议了,我们往下看。4.1路由转发逻辑其实Nacos节点的路由转发逻辑比较简单。先看流程图:步骤如下:①Nacos节点从客户端发起的请求中获取客户端的实例信息,生成一个distroTag,比如IP+端口或者服务名。②Nacos根据distroTag生成hash值。③用哈希值取Nacos节点个数的余数,得到余数,如0、1、2、3。④根据余数从Nacos节点列表中得到指定节点地址。不明白的地方:我这里启动了三个Nacos节点,如下图,三个Running节点。但是为什么Nacos的ServersList多了一个节点192.168.10.197:8848呢?IDEA启动了三个nacos节点,nacos控制台有四个节点。/com/alibaba/nacos/naming/web/DistroFilter.java请求会先到DistroFilter类的doFilter()方法。得到正确的节点地址后,请求就会被转发。获取转发节点地址的代码如下://在Nacos集群中找到目标节点finalStringtargetServer=distroMapper.mapSrv(distroTag);//mapSrv方法会先hash,然后取modulus,responsibleTag的值类似这样:"20.18.7.11:8080"intindex=distroHash(responsibleTag)%servers.size();//distroHash方法会对客户端的ip+port字符串或者服务名stringMath进行hash。abs(responsibleTag.hashCode()%Integer.MAX_VALUE);无论是自己处理注册请求,还是转发给其他节点处理,都会存储实例信息,那么如何存储呢?5、处理请求:来碗Nacos目前有两个版本,v1和v2,如果是v1,则使用instanceController来处理注册请求,否则使用instanceControllerV2。在这篇文章中,我们只解释v1版本是如何处理请求的。先上流程图:添加实例信息的流程测试发起注册的命令:curl-XPOST'http://127.0.0.1:8858/nacos/v1/ns/instance?serviceName=nacos.naming。serviceName&ip=20.18.7.11&port=8080'核心代码是这样的:在服务端注册实例的方法先有一个同步锁,然后执行put操作存储临时实例信息,所以关注一下这个consistencyService。put()方法做的。先看源码:onPut(key,value);//启动一个1s延时任务,同步数据到其他Nacos节点distroProtocol.sync(newDistroKey(key,KeyBuilder.INSTANCE_LIST_KEY_PREFIX),DataOperation.CHANGE,DistroConfig.getInstance().getSyncDelayMillis());这里做了三件事:①将实例信息存储在内存缓存ConcurrentHashMap中。②向BlockingQueue队列中添加一个任务。该任务是将最新的实例列表通过UDP推送给所有的客户端(服务实例),以便客户端获取到最新的服务实例列表。没想到计算机网络的知识终于用上了~③启动1s延时任务,将数据传递给其他Nacos节点。注:对于第二点和第三点,它们是Distro共识协议的一部分,里面的内容还是很多的,下节课再说。下一个知识点预览:这里的存储实例和同步方式和Eureka有什么区别?Eureka使用的三层缓存架构,Nacos使用的CopyOnWrite技术。如何推送给所有客户?UDP方式。如何同步到其他Nacos节点?发行版共识协议。6.小结本文介绍Nacos客户端如何随机选择节点,NacosServer如何路由转发请求,NacosServer如何通过发起注册请求来存储注册实例。另外,本文使用集群环境。如果大家对如何搭建和调试集群环境感兴趣,可以留言,我会在后面补充这部分的讲解。注册请求的核心流程: