nacos源码解析——服务发现


前言

之前我们学习过eureka,知道服务发现有推拉两种方式,nacos也支持两种服务发现方式,一种是直接去nacos服务端拉取某个服务的实例列表,另一种就是服务订阅的方式,就是订阅某个服务,然后这个服务下面的实例列表一旦发生变化,nacos服务端会通过udp的方式通知到客户端。


1、服务发现流程

NamingExample中会有demo,他会去调用NamingService#getAllInstances方法来拉取某个服务的全部实例列表

在这里插入图片描述

1.1、NamingService#getAllInstances

这里会调用一些重载方法最终会到这里,首先他会去判断是否是订阅模式,默认是订阅。我们只看订阅的实现方式,不订阅的方式非常简单,就是直接从服务端拉取服务实例信息就行了。

在这里插入图片描述

1.1.1、ServiceInfoHolder#getServiceInfo

这里如果是订阅模式会首先从本地缓存中获取这个服务的实例列表,这里其实就是根据服务名,组名,集群名生成一个key,然后从map中获取,第一次的时候缓存中是没有的,然后就会向服务端发起请求。

在这里插入图片描述

1.2.1、NamingClientProxy#subscribe

这里本地缓存中没有会调用到这个方法,步骤如下:

  1. 再次尝试从缓存中拉取
  2. 通过proxy向服务端发起订阅请求,因为2.0nacos客户端代理都替换成grpc了,这个我不了解,先看下NamingHttpClientProxysubscribe方法的实现,思想一样。
  3. 添加一个定期更新服务实例列表的定时任务
  4. 根据从服务端拉取到的新实例列表信息,然后和本地比较,如果有更新覆盖本地,并且发布服务实例列表变更的事件,然后写盘。

在这里插入图片描述

1.2.1.1、NamingHttpClientProxy#subscribe

这里会传进去一个udp的端口号。

在这里插入图片描述

1.2.1.1.1、NamingHttpClientProxy#queryInstancesOfService

这里会向服务端发起请求拉取实例列表,这里会把传入的udpPort也带上,请求的服务端地址是/instance/list,对应服务端的接口是InstanceController#list

在这里插入图片描述

1.2.1.1.2、InstanceController#list

这里首先是解析参数,然后把参数包装成Subscriber对象,然后会调用InstanceOperatorServiceImpl#listInstance方法。

在这里插入图片描述

1.2.1.1.3、InstanceOperatorServiceImpl#listInstance

这个方法很长,我把它拆解成几小块,在这块代码中只干了两件事

  1. 从注册表serviceManager中获取服务信息
  2. 判断udpPort是否大于0,由于我们传入了这里是大于0的。然后判断能不能推送,如果可以推送的话就调用NamingSubscriberServiceV1Impl#addClient把当前客户端添加到订阅服务列表中

在这里插入图片描述

第二块大流程分为四个步骤:

  1. 判断Service是否是空的,如果为null则直接组装结果返回给客户端
  2. 使用选择器过滤掉满足的IP,这个可以对IP打标签,可以实现某些IP不给客户端返回,只做一个备份。
  3. 创建一个Map,然后区分出来健康和不健康的实例,这个是通过心跳来维持实例的健康。
  4. 判断健康的实例如果小于保护阈值的时候,那么会将所有不健康的实例也添加到健康里面。(这里的考究可能是如果活着的实例数过少,返回的实例数过少的话可能导致实例过载,而且如果健康实例数过少的话,也有可能是网络分区,可能是服务端和实例发生网络分区,实例本身的状态是好的)

在这里插入图片描述
在这里插入图片描述

1.2.1.1.4、NamingSubscriberServiceV1Impl#addClient

这里讲解一些服务端实例变更异步推送逻辑,这里会将客户端相关信息包装成PushClient,然后添加到ClientMap

在这里插入图片描述在这里插入图片描述

1.2.1.1.5、服务实例变更推送流程图

这个流程之前讲服务注册,下线的时候剖析过,所以我们直接进入发送push消息的流程。

在这里插入图片描述

1.2.1.1.6、UdpPushService#serviceChanged

这里会使用spring的事件通知发出一个事件,这里其实UdpPushService也是这个事件的观察者,这个至于为什么这样做,一来时解耦,二来是方便扩展

在这里插入图片描述

1.2.1.1.7、UdpPushService#serviceChanged

这里是引用

  1. 这里首先做个去重的判断,如果已经存在一个相同的任务,则直接返回
  2. 遍历所有的客户端,然后移除僵尸客户端,然后从缓存中获取数据,包装成AckEntry
  3. 调用udpPushAckEntry推送出去

在这里插入图片描述

1.2.1.1.8、UdpPushService#udpPush

这里就是直接调用udpSocket进行推送,默认重试一次,如果发送失败就进行重试,这里会往AckMapput一个值,这个应该是用来确实收到ACK的,如果收到了就从map中移除,我们看下什么地方调用到了ackMap#remove方法。

在这里插入图片描述

1.2.1.1.9、Receiver#run

我们发现在这个线程的run方法中会做ACK确认,这个线程是在UdpPushService的静态代码块中进行初始化的

在这里插入图片描述
在这里插入图片描述

1.2.1.2.1、PushReceiver(客户端收到通知处理)

PushReceiver会在NamingHttpClientProxy构造方法中进行初始化,在
PushReceiver的构造方法中会开启一个udpSocket,并把PushReceiver作为一个线程放到线程池中。
在这里插入图片描述

1.2.1.2.1、PushReceiver#run
  1. 接受请求,然后对接受的信息进行反序列化,然后根据推送的内容更新本地数据

在这里插入图片描述


版权声明:本文为ZXMSH原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。