文章目录
我们知道服务注册到Nacos之后,Nacos是需要对这些服务实例信息进行保存的,那么Nacos是如何保存的呢?
首先我们先分析Nacos的注册表的结构。
我们知道Nacos有namespace,group,cluster三个分级,他们都是用来进行隔离的。
其中Cluster中分为持久化实例和临时实例。他们都是Set类型。
持久化实例即使服务断开也不会从注册表中被删除,而临时实例会,但是临时实例会在每次服务重启的时候都会再一次注册到注册表中。
所以对于Nacos的注册表,大概结构可以猜测为是三层的Map结构,大概结构如下:
Map<namespace,Map<group,Map<clusterName,Cluster>(serviceName)>>
可以分析得到,Nacos的注册表结构为一个namespace将会拥有多个group组,而每个group组中有多个具体的服务。这些服务以集群的方式出现,因此在cluster集群中,会包含最后的具体的实例。
那么现在大概了解完毕之后,我们看看这个三层的Map结构是如何创建的。
当我们发送一个HTTP请求到Nacos请求注册一个实例的时候,Nacos的服务端将会调用如下方法:
而这个方法最后会调用如下方法,这个方法用于创建Servic实例。
首先是最内层的Map<ClusterName,Cluster>,可以发现,Cluster的内部就是我们刚才说的两个存储临时和持久实例的Set集合。
而这个Service的结构为
而我们可以看到,一开始我们会先调用如下方法,来判断这个Service是否已经创建,如果已经创建直接复用即可。
可以发现这里的返回结果是Service,也就是当前的String其实就是我们的group了。
下面的这个serviceMap结构,第一个String其实就是我们的namespace了。
第二个String其实就是我们的group。一个group中可以有多个的Service,而Service中可以有多个服务的集群。
之后我们开始看如何往Service中放入具体的方法
这里的putService方法就是将我们的服务放入到Map结构中
可以发现为了保证多线程情况下的并发安全,Nacos甚至用了双检索
然后最后一行从刚才的Map<group,Service>结构中获取到对应的group,然后向这个group中放入 服务名称:服务实例。
然后我们再看看init方法,这个init方法其实就是初始化所有服务中的集群的所有的实例的心跳的。
这将会创建一个定时任务,这个定时任务将会获取到所有实例,并且判断15s内是否发送回了心跳,如果没有的话,会将实例的healthy状态设定为false,而如果超过30s,那么当前实例将会被剔除。
了解完毕上述流程,创建完毕Service之后,得到了Service,我们就可以向Service中注入对应的实例了。
而这里进行添加实例是一个全量添加,先获取目前已有的所有实例,并且再添加上当前新实例。
这里有一个Datum,key就是实例名称,value说就是实例具体的信息了。
而这个allIps就可以获取到Cluster中所有的实例
并且这里就开始区分到底是使用CP还是使用AP了。
此时已经完成了从Cluster中获取到所有实例了,那么此时我们就是对他进行一些处理转换即可,那么此时就已经拿到了内存中的全量的实例的信息。
特别注意这里还考虑到了一个CopyOnWrite的思想。
我们知道实例信息是存储在内存中的,此时他先获取到了所有的实例,然后再创建一个新的集合用于存储实例,而不是直接再内存中的Set进行修改添加。
这个方法执行完毕之后,我们就已经将新添加的实例和内存中已经拥有的实例都获取到了。
之后我们就要将这些所有实例放入到cluster中了,但是我们还得选择实例的两种模式。
而这里很明显是一个委托方法,走的是DelegateConsistencyServiceImpl类,具体调用Distro还是Raft需要进行实例类型的判断。
此时会根据key的类型,来判断是放入到临时还是持久服务中。
这里如果选用的是临时实例,那么就是使用Distro来创建,也就是使用AP协议,而如果是持久实例,则是使用Raft。
因为我们知道Raft和Paxos这两个协议都是一致性协议,都是选择CP的。
//AP:EphemeralConsistencyService --->Distro
//CP:PersistentConsistencyService --->Raft
所以,在这里就已经产生了我们的分歧,具体使用AP还是CP取决于你使用的协议。
此时我们先分析,使用AP协议的情况,也就是使用Distro的情况。
那由于保证的是可用性而非一致性,那么其实我们可以使用定时任务或者队列的方式来异步加载这些实例。
而Nacos也正是这么做的。
这里可以看到,又一次的使用了Datum,其中value就是对应的所有的实例,而key就是服务。
这里操作了这么多,其实都只是再另一个内存中进行操作这些实例,还并没有将这些新实例放入到我们的内存中,也就是Nacos的那个set结构中,那个存储具体实例信息的set中。也就是此时还是没有完成实例的注册。
再上面看到的notifier中才是真正的开始执行完成注册事件的地方。
而再notifier的run方法中,当触发CHANGE事件的时候,就会调用如下方法,因为此时阻塞队列已经收到了任务,马上就开始执行逻辑,而如果阻塞队列没有收到任务,那么就会一直阻塞但是不消耗性能。
下面这张图就是具体的更新内存中的实例,并且还会发布一个服务变化事件。
进入到这一步就可以根据是否是临时节点判断应该放入的是临时还是持久Set集合中。
而服务发现查找临时实力的时候就会从这个cluster的ephemeralInstances中进行获取。