Nacos注册中心原理

如下分析基于Nacos 2.2.3版本。

数据模型

在Nacos中,一个服务的确定由三部份信息组成:

  • 命令空间(Namespace):多租户隔离用的,默认是public
  • 分组(Group):用来做环境隔离,比如是测试环境或者是开发环境,默认是DEFAULT_GROUP
  • 服务名(ServiceName):在注册服务必须指定,通常使用类java包名格式,如:nacos.naming.serviceName

其实,在服务里面其实还是有一个集群的概念,在服务注册的时候,可以指定这个服务实例在哪个集体的集群中,默认是在DEFAULT集群下。

1
2
3
4
5
6
7
8
9
10
11
// InstanceForm.java
public class InstanceForm implements Serializable {
// 命名空间,默认值:public
private String namespaceId;
// 分组,默认值:DEFAULT_GROUP
private String groupName;
// 服务名
private String serviceName;
// 集群,默认值:DEFAULT
private String clusterName;
}

在SpringCloud环境底下可以通过如下配置指定集群:

1
2
3
4
5
spring
cloud:
nacos:
discovery:
cluster-name: testCluster

在服务订阅的时候,可以指定订阅哪些集群下的服务实例,如果不指定默认就是订阅这个服务下的所有集群的服务实例。
例如:可以将部署在相同区域的服务划分为同一个集群,比如杭州属于一个集群,上海属于一个集群。这样服务调用的时候,就可以优先使用同一个地区的服务了,比跨区域调用速度更快。

临时实例和永久实例

临时实例和永久实例在底层的许多实现机制是完全不同的。
为什么Nacos要将服务实例分为临时实例和永久实例?
主要还是因为应用场景不同,临时实例就比较适合于业务服务,服务下线之后可以不需要在注册中心中查看到;永久实例就比较适合需要运维的服务,这种服务几乎是永久存在的,比如说MySQL、Redis等等。对于这些服务,需要一直看到服务实例的状态,即使出现异常,也需要能够查看时实的状态。
在Spring Cloud环境底下,一般其实都是业务服务,所以默认注册服务实例都是临时实例。
如果想改成永久实例,可以通过如下配置项来完成:

1
2
3
4
5
spring
cloud:
nacos:
discovery:
ephemeral: false

临时实例

临时实例在注册到注册中心之后仅仅只保存在服务端内部一个缓存中,不会持久化到磁盘,具体来说是保存到了一个ConcurrentHashMap实例中。
这个服务端内部的缓存在注册中心一般被称为服务注册表,当服务实例出现异常或者下线之后,就会把这个服务实例从服务注册表中删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// AbstractClient.java
// 服务注册表
protected final ConcurrentHashMap<Service, InstancePublishInfo> publishers = new ConcurrentHashMap<>(16, 0.75f, 1);
@Override
public boolean addServiceInstance(Service service, InstancePublishInfo instancePublishInfo) {
// 临时实例信息保存在Map结构中
if (null == publishers.put(service, instancePublishInfo)) {
if (instancePublishInfo instanceof BatchInstancePublishInfo) {
MetricsMonitor.incrementIpCountWithBatchRegister(instancePublishInfo);
} else {
MetricsMonitor.incrementInstanceCount();
}
}
NotifyCenter.publishEvent(new ClientEvent.ClientChangedEvent(this));
Loggers.SRV_LOG.info("Client change for service {}, {}", service, getClientId());
return true;
}

@Override
public InstancePublishInfo removeServiceInstance(Service service) {
// 当服务出现异常或者下线之后,将服务实例从注册表中删除
InstancePublishInfo result = publishers.remove(service);
if (null != result) {
if (result instanceof BatchInstancePublishInfo) {
MetricsMonitor.decrementIpCountWithBatchRegister(result);
} else {
MetricsMonitor.decrementInstanceCount();
}
NotifyCenter.publishEvent(new ClientEvent.ClientChangedEvent(this));
}
Loggers.SRV_LOG.info("Client remove for service {}, {}", service, getClientId());
return result;
}

永久实例

永久服务实例不仅仅会存在服务注册表中,同时也会被持久化到磁盘文件中(待验证:在源码中未摘到持久化到文件的实现)。
当服务实例出现异常或者下线,Nacos只会将服务实例的健康状态设置为不健康,并不会对将其从服务注册表中剔除。所以这个服务实例的信息还是可以从注册中心看到,只不过处于不健康状态。

1.x对比2.x

在1.x版本中,一个服务中可以既有临时实例也有永久实例,服务实例是永久还是临时是由服务实例本身决定的。
在2.x版本中,一个服务中的所有实例要么都是临时的要么都是永久的,是由服务决定的,而不是具体的服务实例。

服务注册

所谓的服务注册,就是通过注册中心提供的客户端SDK(或者是控制台)将服务本身的一些元信息,比如ip、端口等信息发送到注册中心服务端。
服务端在接收到服务之后,会将服务的信息保存到前面提到的服务注册表中。

1.x版本的实现

在Nacos在1.x版本的时候,服务注册是通过Http接口实现的。
//TODO