前言

服务下线分为两种,一种是客户端主动调用服务的接口发起下线请求,第二种就是服务故障,然后过长时间没有向服务端发送心跳,然后服务端也会启动一个定时任务,来定时剔除这种故障服务


1、客户端主动请求服务下线

还是通过NamingExample这个例子,如果客户端要主动下线,会调用NamingService#deregisterInstance

nacos中的Spring Event_nacos中的Spring Event

1.1、NamingService#deregisterInstance

这里也会构建出来实例对象,然后执行服务下线逻辑,会调用到clientProxy#deregisterService方法,来发送请求到服务端执行下线逻辑

nacos中的Spring Event_微服务_02

1.2、NamingHttpClientProxy#deregisterInstance

  1. 首先判断是不是临时节点,如果是临时节点会移除心跳逻辑,这个很清楚,服务下线了,也就没必要发送心跳了。
  2. 发送下线请求到服务端,对应服务端的接口是InstanceController#deregister

nacos中的Spring Event_spring cloud_03

1.3、NamingHttpClientProxy#deregisterInstance

  1. 这里从请求中解析出来实例信息
  2. 调用removeInstance移除下线实例

nacos中的Spring Event_nacos中的Spring Event_04

1.4、InstanceOperatorServiceImpl#removeInstance

这里首先从注册表中获取service,判断是否为空,如果不为空则调用serviceManager#removeInstance来移除这个实例。

public void removeInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
        com.alibaba.nacos.naming.core.Instance coreInstance = (com.alibaba.nacos.naming.core.Instance) instance;
        //获取service,校验一下是否为空
        Service service = serviceManager.getService(namespaceId, serviceName);
        if (service == null) {
            Loggers.SRV_LOG.warn("remove instance from non-exist service: {}", serviceName);
            return;
        }
        //移除instance
        serviceManager.removeInstance(namespaceId, serviceName, instance.isEphemeral(), coreInstance);
    }

1.5、ServiceManager#removeInstance

  1. 这里首先获取service,然后加锁,执行移除instance逻辑。
  2. 这里移除实例,首先生成一个key,然后调用substractIpAddresses移除对应的实例,返回剩余的实例列表。
  3. 同步剩余的实例列表到集群中的其他节点,这里同步的逻辑之前注册的时候已经说过了。

nacos中的Spring Event_java_05


nacos中的Spring Event_nacos中的Spring Event_06

2、服务故障下线(心跳续约)

在服务注册的时候,在客户端发送注册请求到服务端的时候,还会创建心跳信息到beatReactor,这个beatReactor就是用来发送心跳的。

nacos中的Spring Event_服务端_07

2.1、beatReactor初始化逻辑

  1. NamingHttpClientProxy的构造方法中会对BeatReactor初始化
  2. BeatReactor构造方法中会初始化一个定时任务线程池。

nacos中的Spring Event_java_08

nacos中的Spring Event_服务端_09

2.2、beatReactor提交心跳任务

在上面已经说了在发送注册实例请求的时候会添加一个心跳任务到beatReactor中。我们看下beatReactor#addBeatInfo

  1. 在方法中首先会生成一个key,然后把心跳信息放到dom2Beat中,这个也是为了防止任务重复,最后会封装一个BeatTask任务到线程池,定时5s执行一次,我们继续看下BeatTask#run方法

nacos中的Spring Event_微服务_10

2.3、beatReactor#run

  1. 这里会往服务端发送心跳
  2. 如果服务端返回NOTFOUND的话,那么就会创建实例信息进行服务注册
  3. 这里再重新往线程池中塞这个任务,这里为什么不定时发送心跳,而每次执行完再手动把任务塞进去呢,这是可以发现他的下一次执行时间不是固定的时间间隔,可以根据服务端的返回来修改下一次发送心跳的时间,做到自适应。
  4. 这里snedBeat会请求服务端的InstanceController#beat接口。

2.4、InstanceController#beat

这里前面都是在进行参数的处理和解析,最后会调用handleBeat来处理心跳,对应InstanceOperatorServiceImpl#handleBeat方法

nacos中的Spring Event_java_11

2.5、InstanceOperatorServiceImpl#handleBeat

  1. 首先从注册表中获取对应的实例信息,如果没有找到,则对其进行注册。
  2. 从注册表获取对应的服务,调用Service#processClientBeat方法处理心跳

nacos中的Spring Event_nacos中的Spring Event_12

2.6、Service#processClientBeat

这里封装了一个ClientBeatProcessor,然后交给了HealthCheckReactor#scheduleNamingHealth方法了,这里就是把ClientBeatProcessor当作任务提交到了线程池中,我们 看下ClientBeatProcessor#run方法

nacos中的Spring Event_java_13

2.7、Service#processClientBeat

  1. 这里首先找到这个集群下面的所有实例,然后判断ip和端口和当前实例都能对的上,则进行续约处理,续约就是修改下最后接到心跳的时间
  2. 然后判断如果实例之前是不健康状态,因为现在又重新收到心跳了,则重新设置为健康状态。
  3. 如果实例健康状态改了,则会把新的实例信息重新推送到订阅这个服务的客户端上面去。

nacos中的Spring Event_微服务_14

3. 故障下线(服务剔除)

之前在进行服务注册的时候讲解过,如果去注册表没有找到Service对象的时候,会创建对应的Service对象并进行初始化,然后会调用到Service#init方法

nacos中的Spring Event_nacos中的Spring Event_15

nacos中的Spring Event_服务端_16

3.1、Service#init

这里在Service的初始化方法中会往健康检查组件中添加一个定时任务,叫ClientBeatCheckTask,每5s执行一次,我们看下ClientBeatCheckTask#run方法。

nacos中的Spring Event_spring cloud_17

3.2、ClientBeatCheckTask#run

  1. 这里会遍历Service下面的所有instance,然后判断instancelastBeat距离当前时间是否已经超过15s了,如果没有被标记,则修改健康状态,推送状态改变的事件到所有的客户端。
  2. 下面有会遍历一边所有的instance,如果超过了30s都没有心跳的话,则删除这个实例,这个deleteIp就是组装一个请求,向本地发送服务下线请求。

nacos中的Spring Event_服务端_18

总结

这里就是讲解了两种下线方式的源码流程,重点是故障下线,实例会通过定时任务发送心跳来进行续约,然后服务端也会启动一个定时任务来定期检测长时间没有收到心跳的实例则会进行剔除。