前言
服务下线分为两种,一种是客户端主动调用服务的接口发起下线请求,第二种就是服务故障,然后过长时间没有向服务端发送心跳,然后服务端也会启动一个定时任务,来定时剔除这种故障服务
1、客户端主动请求服务下线
还是通过
NamingExample
这个例子,如果客户端要主动下线,会调用NamingService#deregisterInstance
1.1、NamingService#deregisterInstance
这里也会构建出来实例对象,然后执行服务下线逻辑,会调用到clientProxy#deregisterService方法,来发送请求到服务端执行下线逻辑
1.2、NamingHttpClientProxy#deregisterInstance
- 首先判断是不是临时节点,如果是临时节点会移除心跳逻辑,这个很清楚,服务下线了,也就没必要发送心跳了。
- 发送下线请求到服务端,对应服务端的接口是InstanceController#deregister
1.3、NamingHttpClientProxy#deregisterInstance
- 这里从请求中解析出来实例信息
- 调用
removeInstance
移除下线实例
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
- 这里首先获取
service
,然后加锁,执行移除instance
逻辑。- 这里移除实例,首先生成一个
key
,然后调用substractIpAddresses
移除对应的实例,返回剩余的实例列表。- 同步剩余的实例列表到集群中的其他节点,这里同步的逻辑之前注册的时候已经说过了。
2、服务故障下线(心跳续约)
在服务注册的时候,在客户端发送注册请求到服务端的时候,还会创建心跳信息到beatReactor,这个beatReactor就是用来发送心跳的。
2.1、beatReactor初始化逻辑
- 在
NamingHttpClientProxy
的构造方法中会对BeatReactor
初始化- 在
BeatReactor
构造方法中会初始化一个定时任务线程池。
2.2、beatReactor提交心跳任务
在上面已经说了在发送注册实例请求的时候会添加一个心跳任务到
beatReactor
中。我们看下beatReactor#addBeatInfo
- 在方法中首先会生成一个
key
,然后把心跳信息放到dom2Beat
中,这个也是为了防止任务重复,最后会封装一个BeatTask
任务到线程池,定时5s
执行一次,我们继续看下BeatTask#run
方法
2.3、beatReactor#run
- 这里会往服务端发送心跳
- 如果服务端返回
NOTFOUND
的话,那么就会创建实例信息进行服务注册- 这里再重新往线程池中塞这个任务,这里为什么不定时发送心跳,而每次执行完再手动把任务塞进去呢,这是可以发现他的下一次执行时间不是固定的时间间隔,可以根据服务端的返回来修改下一次发送心跳的时间,做到自适应。
- 这里
snedBeat
会请求服务端的InstanceController#beat
接口。
2.4、InstanceController#beat
这里前面都是在进行参数的处理和解析,最后会调用
handleBeat
来处理心跳,对应InstanceOperatorServiceImpl#handleBeat
方法
2.5、InstanceOperatorServiceImpl#handleBeat
- 首先从注册表中获取对应的实例信息,如果没有找到,则对其进行注册。
- 从注册表获取对应的服务,调用
Service#processClientBeat
方法处理心跳
2.6、Service#processClientBeat
这里封装了一个
ClientBeatProcessor
,然后交给了HealthCheckReactor#scheduleNamingHealth
方法了,这里就是把ClientBeatProcessor
当作任务提交到了线程池中,我们 看下ClientBeatProcessor#run
方法
2.7、Service#processClientBeat
- 这里首先找到这个集群下面的所有实例,然后判断
ip
和端口和当前实例都能对的上,则进行续约处理,续约就是修改下最后接到心跳的时间- 然后判断如果实例之前是不健康状态,因为现在又重新收到心跳了,则重新设置为健康状态。
- 如果实例健康状态改了,则会把新的实例信息重新推送到订阅这个服务的客户端上面去。
3. 故障下线(服务剔除)
之前在进行服务注册的时候讲解过,如果去注册表没有找到Service对象的时候,会创建对应的Service对象并进行初始化,然后会调用到Service#init方法
3.1、Service#init
这里在Service的初始化方法中会往健康检查组件中添加一个定时任务,叫ClientBeatCheckTask,每5s执行一次,我们看下ClientBeatCheckTask#run方法。
3.2、ClientBeatCheckTask#run
- 这里会遍历
Service
下面的所有instance
,然后判断instance
的lastBeat
距离当前时间是否已经超过15s
了,如果没有被标记,则修改健康状态,推送状态改变的事件到所有的客户端。- 下面有会遍历一边所有的
instance
,如果超过了30s
都没有心跳的话,则删除这个实例,这个deleteIp
就是组装一个请求,向本地发送服务下线请求。
总结
这里就是讲解了两种下线方式的源码流程,重点是故障下线,实例会通过定时任务发送心跳来进行续约,然后服务端也会启动一个定时任务来定期检测长时间没有收到心跳的实例则会进行剔除。