首先,我们先统一各个角色的称呼:
- 这里服务提供者实例,我们统一称为client端,每个实例的唯一标识我们称为clientId,即ip/port信息。
- NacosServer端,我们统一称为server端
注册服务时,NacosServer会为每个注册服务的实例节点维护(缓存)一个Client对象,这个Client对象主要用于:
-
封装(缓存)服务实例列表
-
根据Client对象是否为null来判断client与NacosServer端的连接是否断开,如果连接断开则不能注册服务
在前面《Nacos服务发现基本原理》的文章中,介绍到获取service的实例的流程:
- 首先获取注册service的各个实例节点ip/port集合。至于这个service->实例节点集合是何时怎样缓存的,前文《Nacos缓存注册service的实例节点集合》有说明。其实这一步相当于是根据service获取了实例的index索引信息,后面步骤就是根据这个实例的index索引信息,返回缓存的各个实例。
- 然后根据service实例的clientId(ip/port信息)获取Client对象。
// 根据service实例节点获取对应的Client对象
Client client = clientManager.getClient(clientId);
- 最后由于这个Client对象缓存了真正的实例,所以从Client对象返回缓存的service的实例
我们看到第2步,根据实例节点获取对应的Client对象。那么这个实例Client对象是何时缓存的??又是怎样缓存的???
我们猜想:这个实例Client对象应该是在service注册时,在NacosServer端缓存的。
所以,我们看看NacosServer处理注册的流程。
一、缓存服务实例Client对象的基本流程
1.1 判断是否存在client对象
private void createIpPortClientIfAbsent(String clientId) {
if (!clientManager.contains(clientId)) {
...
clientManager.clientConnected(clientId, clientAttributes);
}
如果不存在client对象,则创建client对象,并维护(缓存)client对象,其实也是维护着与client的连接。
1.2 封装为一个Client对象
基于注册service的实例节点的clientId(即ip/port信息)封装为一个Client对象。这个Client对象含有注册服务的这些实例的ip/port信息。
@Override
public IpPortBasedClient newClient(String clientId, ClientAttributes attributes) {
long revision = attributes.getClientAttribute(REVISION, 0);
// 基于注册service的实例节点的clientId(即ip/port信息)封装为一个Client对象
IpPortBasedClient ipPortBasedClient = new IpPortBasedClient(clientId, true, revision);
ipPortBasedClient.setAttributes(attributes);
return ipPortBasedClient;
}
注意:clientId指的时注册服务的实例节点的ip/port信息。
1.3 然后缓存clientId->Client对象
@Override
public boolean clientConnected(final Client client) {
// 缓存clientId->Client对象
clients.computeIfAbsent(client.getClientId(), s -> {
Loggers.SRV_LOG.info("Client connection {} connect", client.getClientId());
IpPortBasedClient ipPortBasedClient = (IpPortBasedClient) client;
ipPortBasedClient.init();
return ipPortBasedClient;
});
return true;
}
1.4 获取Client对象
前文我们说过,当在服务消费端在发现获取服务的时候:NacosServer端会根据clientId获取Client对象
所以,就能从上面缓存的clients中获取Client对象
@Override
public Client getClient(String clientId) {
return clients.get(clientId);
}
之所以获取Client对象,是因为Client对象中封装了service的实例列表信息。所以最后发现获取服务实例的时候,从Client对象返回缓存的service的实例。
1.5 校验client连接是否断开
如果连接断开,是不能注册服务的。
private void checkClientIsLegal(Client client, String clientId) {
if (client == null) {
Loggers.SRV_LOG.warn("Client connection {} already disconnect", clientId);
throw new NacosRuntimeException(NacosException.CLIENT_DISCONNECT,
String.format("Client [%s] connection already disconnect, can't register ephemeral instance.",
clientId));
}
...
二、最后总结
首先,我们先统一各个角色的称呼:
- 这里服务提供者实例,我们统一称为client端,每个实例的唯一标识我们称为clientId,即ip/port信息。
- NacosServer端,我们统一称为server端
当client发送服务注册请求,server收到请求之后:
- 注册服务之前为每个client维护(缓存)一个Client对象,这个Client对象实际上就是注册service的各个实例节点对应的Client对象。缓存的实际上就是保存:clientId -> Client对象的映射关系缓存到Map中。这个clientId就是注册service的实例节点的唯一标识(ip/port信息)。同时这个Client对象在整个服务注册/发现的流程中还作为判断client连接是否断开的判断依据,可以说这个Client对象代表的就是client连接,维护着client的连接。
- 判断client是否存在(即与client之间的连接是否存在)
- 注册服务
- 注册服务之后,异步发送服务注册事件:即维护(缓存)service->clientId映射关系
判断Client连接是否断开:
private void checkClientIsLegal(Client client, String clientId) {
// 其实就是判断获取到的client是否为null来判断client连接是否断开
if (client == null) {
Loggers.SRV_LOG.warn("Client connection {} already disconnect", clientId);
throw new NacosRuntimeException(NacosException.CLIENT_DISCONNECT,
String.format("Client [%s] connection already disconnect, can't register ephemeral instance.",
clientId));
}
每个注册服务的实例都有一个Client对象与之一 一对应。这个Client对象里面封装了具体实例信息,便于服务发现获取时,从此Client对象中获取实例列表。
所以server端维护Client对象的流程就是这样:
- 服务提供者发起注册请求
- NacosServer根据服务提供者实例的clientId(ip/port信息)生成Client对象
- 缓存clientId->Client对象的映射关系到Map中