一、注册表结构
// com.alibaba.nacos.naming.core.ServiceManager /** * Map(namespace, Map(group::serviceName, Service)). */ private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();
nacos1.X的注册表存于ServiceManager的一个ConcurrentHashMap中,key为命名空间,值又是一个ConcurrentHashMap,内层的ConcurrentHashMapkey为组名+服务名。值为Service服务对象。
这个Service内部又存放了一个hashMap,key为集群名,值为集群对象Cluster。Cluster内部存放两个Set集合,一个存放持久化实例,一个存放临时实例。
// com.alibaba.nacos.naming.core.Service // 这就是serve内部具体存放集群信息位置 private Map<String, Cluster> clusterMap = new HashMap<>();
// com.alibaba.nacos.naming.core.Cluster // 持久化实例集合 @JsonIgnore private Set<Instance> persistentInstances = new HashSet<>(); // 临时实例集合 @JsonIgnore private Set<Instance> ephemeralInstances = new HashSet<>();
实例里面存放了IP、端口、集群名称等信息。
public Instance(String ip, int port) { this.setIp(ip); this.setPort(port); this.setClusterName(UtilsAndCommons.DEFAULT_CLUSTER_NAME); } public Instance(String ip, int port, String clusterName) { this.setIp(ip.trim()); this.setPort(port); this.setClusterName(clusterName); } public Instance(String ip, int port, String clusterName, String tenant, String app) { this.setIp(ip.trim()); this.setPort(port); this.setClusterName(clusterName); this.tenant = tenant; this.app = app; }
以上所有结构组成了nacos1.X的实例注册表。
二、客户端服务注册核心源码
客户端注册实例主要做两件事情,定时发送服务健康状态心跳、发送服务注册请求。
// 在服务启动的时候调用 注册实例 public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException { NamingUtils.checkInstanceIsLegal(instance); String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName); // 如果是临时实例就服务端发心跳,内部是每隔5秒循环调用 if (instance.isEphemeral()) { BeatInfo beatInfo = this.beatReactor.buildBeatInfo(groupedServiceName, instance); this.beatReactor.addBeatInfo(groupedServiceName, beatInfo); } // 注册实例 this.serverProxy.registerService(groupedServiceName, groupName, instance); }
定时发送服务健康状态心跳
如果注册实例是临时实例,则建立心跳机制,原理是发起一个延时5秒的的异步任务向服务端发送健康状态,在这个任务的末尾又调用本身,实现每隔5秒的循环调用,发送心跳。
发送服务注册请求
三、服务端服务注册核心源码
服务端是一个restful风格的HTTP接口
这个接口内会做以下几步;
1. 如果service是第一次创建,则在初始化时创建一个针对该service 的健康检查定时任务,延迟5秒并且每隔5秒执行一次。这个定时任务遍历service下的所有实例,如果发现服务端超过15秒没收到客户端的心跳,则将该实例健康状态标记为false,如果超过30秒没发送心跳过来,则将该实例从注册中心剔除。
如果使用了集群,则这一步只会有一个nacos节点来执行,具体是哪个节点是根据serviceName进行hash运算得到的。
2. 将注册的实例信息放到一个ArrayBlockingQueue阻塞队列中,等待消费。所以这里是一个异常操作。
这个队列的消费逻辑是在nacos启动时开启了一个死循环。
在实际的消费注册服务操作时,用到了写时复制的设计避免读写冲突。
实例权重范围是0.01-10000。
3. 每当服务端有服务信息变化时,比如有新的服务注册、服务修改、删除等,在处理队列异常消息时都会发布一个事件,通知相关客户端服务的变更信息,以提升客户端注册表信息更新的及时性。这是一个从服务端到客户端的UDP请求,udp的端口是在客户端发送获取实例请求时传给后端的,也就是说没有发送给服务发现请求的客户端不可能收到这个udp请求。
四、客户端服务发现源码
1. 客户端会将从服务端拉取的服务信息缓存在本地的map中。服务发现先从本地服务取,没有再从服务端取,取到后放入本地map中。
2.. 除此之外,在请求末尾还会发起一个定时任务,每过5秒从服务端拉取最新的服务信息。