原创不易,转载请注明出处
本文基于nacos1.4.0
前言
nacos作为注册中心,支持两种服务下线,一种是客户端主动调用api向服务端发送服务下线,然后实现服务下线,第二种就是服务故障,然后服务端很长时间没有收到某个实例的心跳信息,服务端就会将这个服务健康状态设置成false,也就是标志不健康状态(这个时间默认是15s,也就是15s服务端没有收到某个服务的心跳信息),如果更长时间没有收到心跳信息,直接就会将这个服务摘除(默认是30s)。关于故障下线的源码实现我们别的篇章会介绍,本文主要是分析下这个服务主动下线部分的源码。
1.客户端请求服务下线
Properties properties = new Properties();
properties.setProperty("serverAddr", "127.0.0.1:8845");
properties.setProperty("namespace","public");
NamingService naming = NamingFactory.createNamingService(properties);
// 先注册
naming.registerInstance("userService", "11.11.11.11", 8888, "DEFAULT");
// 后下线
naming.deregisterInstance("userService", "11.11.11.11", 8888, "DEFAULT");
还是使用example这个子项目中的NamingExample 类为例子。我们可以看到,是先注册,然后调用deregisterInstance 方法进行服务主动下线。这个NameService中服务下线的api 重载也是非常的多
可以使用serviceName, group,cluster不同条件来进行服务下线。如果你group没有设置就是使用默认的DEFAULT_GROUP
,如果你集群没有设置的话,也是使用默认的DEFAULT
, serviceName , ip,port这三个条件必须得有。
会封装成一个instance,把serviceName,ip,port,集群都塞进去
如果是临时节点的话,就会调用beatReactor这个组件移除beatInfo这个任务,就是不用再发送心跳续约了。接着就是调用serverProxy这个组件的deregisterService 方法进行服务下线,这里需要注意的是NamingUtils.getGroupedName(serviceName, groupName)
这行代码,它是将group与serviceName 使用@@ 拼到了一起,然后作为这个服务serviceName, 我们在服务注册的时候也是这样拼装过,所以服务下线的时候也需要拼装下。
组装请求参数,然后是调用reqApi 发送请求,请求的uri是/nacos/v1/ns/instance
, 请求方式是delete ,也就是使用restful风格。
其实再往下就与服务注册客户端请求的代码一样了,这里就是先随机选择一个server,然后调用callServer方法进行发送,如果异常的话,就往下轮询其他server进行发送请求,你有多少个server 就重试多少次。往下的代码与服务注册请求都一样,其实就是拼装url,发送请求,好了, 客户端的主动下线部分我们就解析完成了。
2.服务端处理服务主动下线
客户端请求会被InstanceController 这个controller处理,其实与instance有关的请求都是这个controller 来处理。InstanceController的deregister这个方法就是服务下线。
前面代码都是解析请求参数的,我们需要关注serviceManager.getService 与serviceManager.removeInstance 这两个行代码的调用,serviceManager.getService 其实就是根据namespace与serviceName 从serviceMap 这个map中获取对应的service 实例,如果没有的话,就说明没有之前没有注册过,也就直接返回ok了,如果存在的话,就会调用serviceManager.removeInstance 移除这个instance。
这个方法没啥好看的,先获取一下这个namespace与serviceName对应的service实例,然后加锁,调用removeInstance 方法进行移除。
先生成一个服务列表的key这个key与你instance是否是临时节点有关系,如果是临时节点,生成的key是这个样子的com.alibaba.nacos.naming.iplist.ephemeral.{namespace}##{serviceName}
永久节点就是com.alibaba.nacos.naming.iplist.{namespace}##{serviceName}
这个样子。
接着就是调用substractIpAddresses 方法用之前的instance列表减去 这次要下线的实例列表,然后生成一份新的剖去下线的实例列表。
UPDATE_INSTANCE_ACTION_REMOVE这个action是remove。接着调用updateIpAddresses 方法。
其实就是将之前的instance弄出来,然后放到这个instanceMap中,然后遍历这个要删除的instance集合,如果是删除action的话,就从instanceMap中移除这个DatumKey,这个key就是这个样子 ip:port:unknown:{cluster}
。到最后这个instanceMap就剩下抛去我们要下线的instance了。
接着removeInstance 这个方法往下看,就是创建一个instances对象,然后将instance集合塞到这里面。接着就是交给consistencyService组件来进行put操作。这里其实就是调用了EphemeralConsistencyService的实现类DistroConsistencyServiceImpl 的put方法。往下的步骤我们就不赘述了,再往下就与服务注册后续的逻辑一摸一样的,就是封装instance集合与key ,封装成一个Datum,然后将这个Datum 塞到dataStore 这个存储组件中,这个组件实际就是个map。
接着就是往Notifier 这个组件里面的一个task队列添加一个事件通知任务,然后就完事了(其实这里还有向其他server 同步的步骤,我们这里暂时先不研究),就可以将响应返回给客户端了。这个时候,其实service 对象里面的instance列表并没有更新,这就是所谓nacos的异步注册异步下线,会有一个后台线程,不停的从Notifier组件中的task 队列中取出task,然后调用handle方法进行事件通知,其实就通知到service 对象的onChange 方法里面了,其实更新操作都是这个方法做的。具体的源码分析可以看下《深度解析nacos源码之注册中心(服务注册)》这篇文章,因为后面都是重复的内容,我们这里也就不再赘述了。也可以看下《深度解析nacos源码之注册中心(服务注册流程图补充)》这篇文章,然后好好理解下这个nacos所谓的异步注册是怎样实现的。