1.3 服务注册
1.3.1 服务注册流程
创建完service后, 会重新从缓存中获取service,之后便会向service中注册服务,ServiceManager#addInstance()方法:
1public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips) throws NacosException{
2 // 生成服务唯一的key:"com.alibaba.nacos.naming.iplist."+(是否为瞬时节点)"ephemeral"+"namespaceId"+"##"+"service:spring.application.name"
3 String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);
4 // 获取服务列表,需要将实例放入到指定的服务下
5 Service service = getService(namespaceId, serviceName);
6 // 进行同步操作
7 synchronized (service) {
8 // 添加到service所在的实例列表下,返回的是更新后新的实例列表
9 List instanceList = addIpAddresses(service, ephemeral, ips);
10 // 创建新的Instances持久化对象
11 Instances instances = new Instances();
12 instances.setInstanceList(instanceList);
13 // 使用一致性协议,存储更新后的实例数据
14 consistencyService.put(key, instances);
15 }
16}
生成服务唯一的key,命名格式为"com.alibaba.nacos.naming.iplist."+"ephemeral."+namespaceId+"##"+"服务的spring.application.name",举一个例子可以是:"com.alibaba.nacos.naming.iplist.ephemeral.public##sunshine-taurus"。
获取需要将服务注册到的service。
注册服务时,需要对service进行同步操作。
将注册的服务实例添加到service的实例列表中,并返回最新的service服务列表。
使用Distro协议,存储更新后的实例数据。
注意,构建的key不是为了注册的服务实例而用,而是为了service的服务列表而用。
因为每个服务spring.application.name是相同的。
1.3.2 注册服务到服务列表
通过调用ServiceManager#addIpAddress()方法,将注册服务添加到当前service的服务列表中:
1public List addIpAddresses(Service service, boolean ephemeral, Instance... ips) throws NacosException{
2 return updateIpAddresses(service, UtilsAndCommons.UPDATE_INSTANCE_ACTION_ADD, ephemeral, ips);
3}
使用UPDATE_INSTANCE_ACTION_ADD添加操作,调用ServiceManager#updateIpAddresses()方法更新服务实例列表。
接下来会对ServiceManager#updateIpAddresses()方法进行逐步分析。
1.3.2.1 获取已有服务实例
首先,Nacos服务端会从一致性协议,对于临时节点,也就是从内存中获取指定service临时实例节点数据:
1// 从一致性协议中读取当前service的实例数据,如果是临时节点使用Distro协议,如果是持久节点使用Raft协议
2Datum datum = consistencyService.get(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), ephemeral));
3// 获取当前Nacos上所有的临时实例列表
4List currentIPs = service.allIPs(ephemeral);
5Map currentInstances = new HashMap<>(currentIPs.size());
6Set currentInstanceIds = Sets.newHashSet();
7// 遍历当前所有的服务列表,放入到"ip:port"=>instance字典表,和实例ID集合中
8for (Instance instance : currentIPs) {
9 currentInstances.put(instance.toIPAddr(), instance);
10 currentInstanceIds.add(instance.getInstanceId());
11}
首先,从缓存中获取指定serviceKey的实例信息。
接着,从service缓存中获取所有临时节点信息。
将service缓存中的实例信息,转换为"ip:port"=>instance集合,instanceId集合。
1.3.2.2 更新缓存信息
接着,Nacos会以一致性协议中取出的实例数据为主,缓存的中的数据为辅,对实例数据进行更新:
1Map instanceMap;
2if (datum != null) {
3 // 当前存在指定service的数据,则对集群的数据进行更新后,使用此缓存
4 instanceMap = setValid(((Instances) datum.value).getInstanceList(), currentInstances);
5} else {
6 // 否则会创建一个新缓存
7 instanceMap = new HashMap<>(ips.length);
8}
接着调用ServiceManager#setValid()方法对数据进行更新:
1private Map setValid(List oldInstances, Map map){
2
3 Map instanceMap = new HashMap<>(oldInstances.size());
4 // 迭代缓存中所有指定service一致性协议数据中所有的实例
5 for (Instance instance : oldInstances) {
6 // 从集群缓存中获取实例信息
7 Instance instance1 = map.get(instance.toIPAddr());
8 if (instance1 != null) {
9 // 使用集群中信息,更新数据中的实例信息
10 instance.setHealthy(instance1.isHealthy());
11 instance.setLastBeat(instance1.getLastBeat());
12 }
13 // 重新放入到集群实例缓存中
14 instanceMap.put(instance.getDatumKey(), instance);
15 }
16 return instanceMap;
17}
在ServiceManager#setValid()方法中,主要做了:
遍历一致性协议中存储的实例列表。
使用service缓存中存储的实例信息,更新一致性协议中存储的实例信息。
返回更新后的一致性协议中存储的实例列表。
此时,已有数据列表的信息已经是最新的了。
1.3.2.3 添加实例
接下来,需要遍历所有需要添加的实例,添加到已有的实例集合中:
1for (Instance instance : ips) {
2 // 如果需要更新的实例所在集群不存在,则需要创建一个新集群
3 if (!service.getClusterMap().containsKey(instance.getClusterName())) {
4 Cluster cluster = new Cluster(instance.getClusterName(), service);
5 // 初始化集群,开始进行集群的健康检查任务
6 cluster.init();
7 // 将新创建的集群添加到指定service中
8 service.getClusterMap().put(instance.getClusterName(), cluster);
9 Loggers.SRV_LOG.warn("cluster: {} not found, ip: {}, will create new cluster with default configuration.",
10 instance.getClusterName(), instance.toJSON());
11 }
12 if (UtilsAndCommons.UPDATE_INSTANCE_ACTION_REMOVE.equals(action)) {
13 // 如果是移除操作,则移除当前的实例
14 instanceMap.remove(instance.getDatumKey());
15 } else {
16 // 其他操作,也就是添加或更新,则直接覆盖缓存的实例对象
17 instance.setInstanceId(instance.generateInstanceId(currentInstanceIds));
18 instanceMap.put(instance.getDatumKey(), instance);
19 }
20
21}
22// 如果在添加操作后,指定service中没有实例,证明出现了异常,需要排查问题
23if (instanceMap.size() <= 0 && UtilsAndCommons.UPDATE_INSTANCE_ACTION_ADD.equals(action)) {
24 throw new IllegalArgumentException("ip list can not be empty, service: " + service.getName() + ", ip list: "
25 + JSON.toJSONString(instanceMap.values()));
26}
如果需要注册服务实例所在的cluster不存在,则会创建一个新的cluster,并关联到service上。
如果此次更新操作类型是移除操作,则从已有实例集合中,移除指定实例。
除了移除操作,其他情况下只能是添加操作,会生成实例的新instanceId,紧接着添加或更新实例集合中的实例数据。
在ServiceManager#updateIpAddresses()方法中开始部分创建的instanceId集合,用于在生成新实例的instanceId时,为SnowFlake算法提供不重复的序号。
注册完服务实例后,则返回更新后实例列表。
1.3.3 更新一致性协议数据
在注册服务实例过程中,实例列表是从一致性协议缓存中获取的。
但是在注册完成之后,并没有在ServiceManager#updateIpAddresses()方法中并没有对一致性协议中的数据进行更新,我个人认为是方法职责问题。
所以在注册完服务实例后,紧接着就是对新的service服务列表的数据进行一致性协议的更新。
注册成功后,即完成服务的service注册。