• nacos源码深度剖析理解之注册中心


    服务注册

    客户端 

    从客户端出发分析nacos服务注册的流程,spring boot应用集成nacos客户端代码,服务在启动过程中,将自己的元数据信息(ip\port\服务名称等)封装为一个instance对象,通过http请求发送给nacos服务注册中心。nocos服务收到请求并解析它,将它封装为service对象存入serviceMap(Map>)中。

    spring boot集成nacos客户端:spring boot使用自动装配技术在启动的过程中会解析相关的AutoConfiguration类,来加载某些需要加载到spring容器的bean,而它也是通过自动装配实现的。

    1. <dependency>
    2. <groupId>com.alibaba.cloudgroupId>
    3. <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
    4. dependency>

    通过引入上面的maven配置,spring boot会加载NacosDiscoveryAutoConfiguration自动装配类,它会像spring容器注册3个bean,其中核心bean为NacosAutoServiceRegistration。

    NacosAutoServiceRegistration的父类实现了ApplicationListener接口,我们知道spring容器在调用refresh()方法时会发布事件,而实现了这个接口的类的onApplicationEvent(event)方法就会执行

    紧接着来到start()方法

    执行register()方法是NacosAutoServiceRegistration类中的

    父类的register()方法会调用NacosServiceRegistry#register()方法真正开始执行注册逻辑,首先来看下这个方法的参数Registration,这个是怎么来的呢?回到NacosDiscoveryAutoConfiguration类,它会注册三个bean,而这个参数就是其中一个bean,即NacosRegistration,这个bean封装了该服务的配置信息(NacosDiscoveryProperties)和context,真相大白。

    随后从registration中获取服务名称serviceId,获取分组信息,解析该registration对象封装为Instance对象,调用NacosNamingService#registerInstance()向nacos服务发起请求和心跳保活。

    解释下instance.isEphemeral(),目前所分析的默认架构是AP的临时实例,所以instance的ephemeral默认为true,表示nacos服务是非持久化服务信息的,通过保持心跳来维护服务是否处于存活状态。这不上面代码已经开始构建BeatInfo对象了,调用BeatReactor.addBeatInfo()

    创建BeatTask任务对象,获取任务执行的延迟时间将他们作为参数开始异步定时执行任务:

    定时任务创建完成后,紧接着调用NamingProxy.registerService()方法想nacos server注册服务实例。

    至此,注册流程客户端分析结束。

    nacos服务端

    我们要知道nacos服务也是基于spring boot开发的,故对外提供接口的方式也是基于springMVC实现的,从客户端可以知道服务注册的请求url是/nacos/v1/ns/instance,所以可以直接找到InstanceController的注册服务接口:

    首先解析namespace,service Name,解析request构建Instance对象,随后看两个核心方法createEmptyService()和ServiceManager.registerInstance():

    createEmptyService():先尝试从serviceMap(他就是存储服务实例元数据的map,前面已经提了它的结构)获取service,第一次注册肯定获取不到则回去new Service(),通过namespaceId,serviceName将空的service对象put到serviceMap对象中:

    然后对service进行初始化service.init():

     其中有一个客户端的心跳检查:

    ClientBeatCheckTask任务核心的检查代码,检查实例心跳时间是否超过15s,如果超过了15s则将该实例的健康状态设置为false,然后会发布服务变更事件,该事件会遍历所有订阅该服务实例的服务,通过UDP协议发送出去,当然这里面还有客户端的时间判断,只有符合时间要求的客户端才会发送更新请求。再发布实例心跳超时事件,但是nacos并没有处理该事件

     

    随后从serviceMap中获取service,调用addInstance(),添加实例instance到服务service:

    addIpAddresses()主要是对instance做了些比较和处理并返回了集合,有兴趣可自行研究,我们步入主线,进入DistroConsistencyServiceImpl.put()方法

    onPut()调用Notifier.addTask(),将key和action(DataOperation.CHANGE)封装起来添加到有界阻塞队列中

    任务丢尽队列了,那肯定有其他的线程去处理任务,这不,有个专门的单线程线程池处理这个任务

     handler()里面会调用Service#onChange(),而它会调用updateIPs()更新之前put到serviceMap中的service对象的相关属性。在更新的时候,有个类想聊下就是Cluster,说下我对源代码注释的理解,Service包含多个Cluster,Cluster包含多个Instance,而在代码层面也是这个关系:

    官方文档我没有看到对Service-->{Map}的解释,但是我看到了一个虚拟集群的概念,我想应该就是对应这个的:同一个服务下的所有服务实例组成一个默认集群, 集群可以被进一步按需求划分,划分的单位可以是虚拟集群。

    最后总结一张流程图,希望对大家有帮助(还包含心跳保活)

    服务发现

    我想大家都知道服务发现就是微服务从注册中心中拉取其他服务注册到nacos服务的元数据信息,但是你们有思考过这个服务发现发起点是谁吗或者在哪吗?今天就来分析分析

    客户端

    我们知道spring cloud通过Feign发起http协议的远程调用,而Feign通过Ribbon负载均衡策略发起HTTP调用,所以nacos通过实现Ribbon的负载均衡策略来整合服务发现机制。另外Ribbon默认使用机房区域的ZoneAwareLoadBalancer负载均衡器,直接从创建ZoneAwareLoadBalancer对象开始找服务发现的入口RibbonClientConfiguration.ribbonLoadBalancer();值得注意的是,这个Bean是延迟加载的,真正加载是在发起Feign的时候(本人并没有验证,会单独出一期)。

    1. @Bean
    2. @ConditionalOnMissingBean
    3. public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList serverList, ServerListFilter serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
    4. return (ILoadBalancer)(this.propertiesFactory.isSet(ILoadBalancer.class, this.name) ?
    5. (ILoadBalancer)this.propertiesFactory.get(ILoadBalancer.class, config, this.name) :
    6. new ZoneAwareLoadBalancer(config, rule, ping, serverList, serverListFilter, serverListUpdater));
    7. }

    接下来从创建ZoneAwareLoadBalancer对象开始分析

    从下面开始就进入nacos源代码 

    继续跟踪NacosNamingService的selectInstances()方法,真正开始对nacos服务发起http请求获取服务列表

    先尝试调用getServiceInfo0()从本地获取缓存服务列表,显然第一次发起调用没有缓存,

    故调用updateServiceNow()

    封装http请求向nacos服务发起服务调用,获取对应服务名称的服务列表 

    至此,请求发出去以后就等到客户端返回result,随之调用ProcessServiceJSON(result)处理返回结果,解析result缓存到serviceInfoMap中。

    然后调用scheduleUpdateIfAbsent(),向futureMap添加一个UpdateTask任务

    任务具体做了什么呢?就是每隔1s会执行updateServiceNow()方法向nacos服务发起更新本地服务缓存列表的请求。

    服务端

    根据客户端发起http请求的url地址,找到InstanceController.list()方法,在调用doSrvIpxt()之前,都是在解析request参数为调用方法做准备

     进入doSrvIpxt()方法后,通过serviceManager.getService()方法从serviceMap获取对应namespace,serviceName的服务应用,然后尝试开启push功能,拿着又是什么东东呢?娓娓道来,判断udpPort是否大于0,一般来讲,这里已经在客户端设置好了,都会满足条件。

    而pushService.canEnablePush()表示支持的语言或者请求的类型,我这里是java调用肯定是返回true的。

    所以会调用pushService.addClient()方法,将该客户端加入到订阅该服务的订阅者名单里进行缓存,后期更新该服务,会通过订阅者缓存信息进行udp更新推送。

    将获取的Service进行改装,封装为ObjectNode对象返回。

    最后服务发现的流程总结

    心跳保活

    客户端

    回到服务注册流程,在服务注册之前会创建BeatInfo对象

    调用addBeatInfo()方法,然后创建BeatTask任务提交到定时任务的线程池 

    该任务只干一件事,就是每隔5s给nacos服务发送心跳请求:/instance/beat 

    服务端

    服务端总结起来并不复杂,只是根据请求参数从serviceMap中获取对应instance和service,然后创建ClientBeatProcessor客户端心跳处理任务,遍历instances更新当前instance实例心跳时间进行保活。具体的代码不贴了,流程总结请看服务注册流程。

    服务更新

    客户端

    删除操作——服务停止 

    当我们shutdown某个服务后,意味着该服务中spring容器生命周期的销毁,会回调LifeCycle的stop()方法,其中spring cloud实现的AbstractDiscoveryLifecycle抽象类实现了SmartLifecycle接。类,故它的stop()在服务停止的时候会被回调执行

    通过cas操作保证只有一个线程会执行服务注销逻辑,最终会执行NacosServiceRegistry#deregister()

     

    在removeBeatInfo()中首先从dom2Beat(这是BeatInfo的map集合,它缓存了所有存活服务的心跳任务)中移除正在执行的心跳任务, 然后将返回的BeatInfo的stopped属性置为false,在执行BeatTask任务时会先判断beatInfo的stopped属性,为false也就意味着停止发送心跳请求。

    随后就是调用serverProxy.deregisterService(),向nacos发送注销服务请求

    服务端 

    从上面的url可知,注销服务url和注册服务url相同,区别就是http请求方法不同,这里是DELETE,找到对应的核心处理方法serviceManage.removeInstance();

    又调用substractIpAddresses(),从方法的命名不难看出,就是从减去不用的IP地址(实例)

    最终,还是通过调用下面的updateIpAddresses()方法进行处理,前面已经看过了,再总结下,就是通过复制一份Map集合,然后从这个集合中把需要注销的服务进行remove,返回这个map集合。再调用onPut()方法将key和action(删除操作)封装为Pair对象提交到任务队列,再由其他线程处理Notifier任务,只不过这里是删除操作,这里其实跟注册逻辑相同,只不过走的代码分支略有不同,处理instance的方式也不同。

    服务变更和服务注册逻辑几乎重叠,可自行仔细琢磨。 

    小结

    本文主要分析了nacos注册中心的服务注册、服务发现、心跳保活和服务注销的源码逻辑。值得学习的地方,服务注册、服务变更、服务注销对请求的异步队列处理,提升吞吐量;其次,nacos在设计心跳保活时在任务的run()中把自己作为一个新的任务重新放入线程池进行延迟执行;nacos服务端收到服务变更请求后,会主动向订阅了该服务的实例进行udp推送变更,就算推送失败,最终客户端实例也会有每隔5s向nacos服务请求最新的服务实例信息,然后进行缓存。

  • 相关阅读:
    平安人寿“内鬼”泄露近4万条公民信息
    计算机竞赛 深度学习疲劳检测 驾驶行为检测 - python opencv cnn
    std::any和枚举类转换
    可编程的并行接口8255A
    [题] 最长连续不重复子序列
    mysql 创建函数
    1818_ChibiOS的计数信号量
    TopK问题详解
    深信服AC应用控制技术
    Golang 编码规范
  • 原文地址:https://blog.csdn.net/weixin_36279234/article/details/126665196