2. 服务端处理客户端的服务注册请求流程
从 Nacos 客户端的服务注册流程可知,客户端使用 RpcClient 向服务端发送一个 RequestInstance 请求。而 RequestInstance 请求到服务端后,则使用 InstanceRequestHandler 来处理。
服务端处理客户端注册请求
在 InstanceRequestHandler 类中,定义了 handle 方法,这个方法就是具体处理请求的
// InstanceRequestHandler
@Override
@Secured(action = ActionTypes.WRITE)
public InstanceResponse handle(InstanceRequest request, RequestMeta meta) throws NacosException {
// 创建了一个 service 对象,里面包含了 命名空间、分组名、服务名。
Service service = Service
.newService(request.getNamespace(), request.getGroupName(), request.getServiceName(), true);
switch (request.getType()) {
case NamingRemoteConstants.REGISTER_INSTANCE:
// 处理注册服务实例
return registerInstance(service, request, meta);
case NamingRemoteConstants.DE_REGISTER_INSTANCE:
// 处理注销服务实例
return deregisterInstance(service, request, meta);
default:
throw new NacosException(NacosException.INVALID_PARAM,
String.format("Unsupported request type %s", request.getType()));
}
}
- handler 方法首先是根据请求参数创建一个 Service 对象,然后根据是注销请求还是注册请求调用不同的方法
由于在客户端发送的是注册请求,所以 handle 方法进入 registerInstance() 方法
// InstanceRequestHandler
private InstanceResponse registerInstance(Service service, InstanceRequest request, RequestMeta meta) {
// 调用了注册方法,在这里我们要注意这些参数:
// service 就是我们刚刚在 swtich 之前创建的一个新的 service 对象,里面有命名空间、分组名、服务名
// request.getInstance() 这个就是对应客户端的实例对象,里面肯定有 ip、端口等信息
// meta.getConnectionId() 这个很关键,是连接Id
clientOperationService.registerInstance(service, request.getInstance(), meta.getConnectionId());
return new InstanceResponse(NamingRemoteConstants.REGISTER_INSTANCE);
}
- registerInstance() 方法最终会调用 clientOperationService 的 registerInstance 方法。clientOperationService 的实现类为 EphemeralClientOperationServiceImpl
// EphemeralClientOperationServiceImpl
@Override
public void registerInstance(Service service, Instance instance, String clientId) throws NacosException {
// 检查 instance 是否合法
NamingUtils.checkInstanceIsLegal(instance);
// 获取一个 singletonService
Service singleton = ServiceManager.getInstance().getSingleton(service);
if (!singleton.isEphemeral()) {
throw new NacosRuntimeException(NacosException.INVALID_PARAM,
String.format("Current service %s is persistent service, can't register ephemeral instance.",
singleton.getGroupedServiceName()));
}
// 通过 连接ID 获取对应的 Client 对象。
// clientManager 为 ClientManagerDelegate
Client client = clientManager.getClient(clientId);
if (!clientIsLegal(client, clientId)) {
return;
}
// 把我们 instance 里面的信息,放到了 InstancePublishInfo 类型的对象里面去
InstancePublishInfo instanceInfo = getPublishInfo(instance);
// 往 client 对象里添加服务实例。
client.addServiceInstance(singleton, instanceInfo);
client.setLastUpdatedTime();
client.recalculateRevision();
// 发布客户端注册服务事件
NotifyCenter.publishEvent(new ClientOperationEvent.ClientRegisterServiceEvent(singleton, clientId));
// 发布服务实例元信息事件
NotifyCenter
.publishEvent(new MetadataEvent.InstanceMetadataEvent(singleton, instanceInfo.getMetadataId(), false));
}
registerInstance 方法主要分为一下几个步骤:
- 检查 instance 是否合法
- 根据传递的 service 参数获取一个新的 service,命名为 singleton
- 从 clientManager 中获取 client id 对应的 client。
- 向 client 找那个添加服务实例。
- 发布相应的事件。
检查 instance 是否合法
// NamingUtils
public static void checkInstanceIsLegal(Instance instance) throws NacosException {
if (instance.getInstanceHeartBeatTimeOut() < instance.getInstanceHeartBeatInterval()
|| instance.getIpDeleteTimeout() < instance.getInstanceHeartBeatInterval()) {
throw new NacosApiException(NacosException.INVALID_PARAM, ErrorCode.INSTANCE_ERROR,
"Instance 'heart beat interval' must less than 'heart beat timeout' and 'ip delete timeout'.");
}
if (!StringUtils.isEmpty(instance.getClusterName()) && !CLUSTER_NAME_PATTERN.matcher(instance.getClusterName()).matches()) {
throw new NacosApiException(NacosException.INVALID_PARAM, ErrorCode.INSTANCE_ERROR,
String.format("Instance 'clusterName' should be characters with only 0-9a-zA-Z-. (current: %s)",
instance.getClusterName()));
}
}
- checkInstanceIsLegal 方法主要是检测示例的心跳参数和集群名称是否合法。
获取 service
// ServiceManager
private final ConcurrentHashMap<Service, Service> singletonRepository;
private final ConcurrentHashMap<String, Set<Service>> namespaceSingletonMaps;
public Service getSingleton(Service service) {
// 添加一个 service,如果存在就不添加
singletonRepository.putIfAbsent(service, service);
// 然后从这个 Map 中把 Service 取出来
Service result = singletonRepository.get(service);
// 以 命名空间 为 key,把相同命名空间的 service 加入到 namespaceSingletonMaps 当中
namespaceSingletonMaps.computeIfAbsent(result.getNamespace(), (namespace) -> new ConcurrentHashSet<>());
namespaceSingletonMaps.get(result.getNamespace()).add(result);
return result;
}
- getSingleton() 的主要逻辑:
- 判断 service 是否存在于 singletonRepository 中,如果不存在,则将 service 参数添加到 singletonRepository,如果存在则不添加
- 从 singletonRepository 拿出 service,这个 service 可能是参数传入的 service,也可能是 singletonRepository 原有保存的 service
- 拿到 service 之后,再向 namespaceSingletonMaps 中加入 service,namespaceSingletonMaps 的 key 为 service 的命名空间,value 为相同命名空间的 service 列表。
获取 Client 对象
调用 clientManager 通过连接 ID 获取对应的 Client 对象。clientManager 的实现类为 ClientManagerDelegate,ClientManagerDelegate 是 ClientManager 的代理类,ClientManagerDelegate 根据不同类型的 clientId,返回不通用的 ClientManager 实现类,
// EphemeralIpPortClientManager
private final ConcurrentMap<String, IpPortBasedClient> clients = new ConcurrentHashMap<>();
@Override
public Client getClient(String clientId) {
return clients.get(clientId);
}
- getClient() 方法从一个 Map 当中,获取了一个 Client 对象。
当客户端使用 grpc client 连接 Nacos 服务端时,客户端和服务端之间会建立一个长连接,当长连接建立好了之后,在服务端会产生一个 socketChannel 的连接对象,这个对象代表着客户端。而 Nacos 中的 gRPC 底层其实就是借助于 Netty 服务,在 socketChannel 对象的基础之上,Nacos 又封装了一层 Client 对象,并且会生成一个 connectionId 把它们关联起来
向 client 找那个添加服务实例
// 往 client 对象里添加服务实例。
client.addServiceInstance(singleton, instanceInfo);
client.setLastUpdatedTime();
client.recalculateRevision();
首先会调用 client 的 addServiceInstance 方法,存储客户端 Instance 信息
// AbstractClient
protected final ConcurrentHashMap<Service, InstancePublishInfo> publishers = new ConcurrentHashMap<>(16, 0.75f, 1);
public boolean addServiceInstance(Service service, InstancePublishInfo instancePublishInfo) {
// 第一次 put 的service,返回为 null
if (null == publishers.put(service, instancePublishInfo)) {
// 监视器记录
MetricsMonitor.incrementInstanceCount();
}
// 发布客户端改变事件
NotifyCenter.publishEvent(new ClientEvent.ClientChangedEvent(this));
Loggers.SRV_LOG.info("Client change for service {}, {}", service, getClientId());
return true;
}
- 这里会将 client 加入到 publisher 这个 Map 中,Map 的 Key 就是对应我们这个实例的 service,Value 就是对应 Instance 实例信息。
- 加入完 publisher 之后,监视器的记录为加一,监视器的目的是为了统计实例数量。
- 最后,发布客户端改变的事件。这个事件会被 DistroClientDataProcessor 类消费,DistroClientDataProcessor 类最终会将客户端信息同步到其他的 Nacos 服务器。
存储客户端 Instance 信息完毕后。会设置 lastUpdatedTime 属性为最新的事件,然后重新计算 client 的版本信息。
// AbstractClient
@Override
public void setLastUpdatedTime() {
this.lastUpdatedTime = System.currentTimeMillis();
}
@Override
public long recalculateRevision() {
int hash = DistroUtils.hash(this);
revision.set(hash);
return hash;
}
发布相应的事件
更新完 client 的属性之后,会发布了两个事件,客户端服务注册事件、实例元数据事件。
// 发布客户端注册服务事件
NotifyCenter.publishEvent(new ClientOperationEvent.ClientRegisterServiceEvent(singleton, clientId));
// 发布服务实例元信息事件
NotifyCenter
.publishEvent(new MetadataEvent.InstanceMetadataEvent(singleton, instanceInfo.getMetadataId(), false));
ClientOperationEvent.ClientRegisterServiceEvent 事件的处理逻辑
ClientRegisterServiceEvent 会通知到 ClientServiceIndexesManager 类的 onEvent 方法
// ClientServiceIndexesManager
@Override
public void onEvent(Event event) {
if (event instanceof ClientEvent.ClientDisconnectEvent) {
handleClientDisconnect((ClientEvent.ClientDisconnectEvent) event);
} else if (event instanceof ClientOperationEvent) {
handleClientOperation((ClientOperationEvent) event);
}
}
onEvent方法中判断了两个事件类型,其中一个便是服务注册发布的是ClientOperationEvent类型的事件,所以会进入到 handleClientOperation() 方法
// ClientServiceIndexesManager
private void handleClientOperation(ClientOperationEvent event) {
Service service = event.getService();
String clientId = event.getClientId();
// 处理客户端注册事件
if (event instanceof ClientOperationEvent.ClientRegisterServiceEvent) {
addPublisherIndexes(service, clientId); // 处理 ClientRegisterServiceEvent 方法
// 处理客户端注销事件
} else if (event instanceof ClientOperationEvent.ClientDeregisterServiceEvent) {
removePublisherIndexes(service, clientId);
// 处理客户端订阅事件
} else if (event instanceof ClientOperationEvent.ClientSubscribeServiceEvent) {
addSubscriberIndexes(service, clientId);
// 处理客户端取消订阅事件
} else if (event instanceof ClientOperationEvent.ClientUnsubscribeServiceEvent) {
removeSubscriberIndexes(service, clientId);
}
}
- handleClientOperation 会判断四种事件类型,包括客户端的注册、注销,也有客户端的订阅/取消订阅,由于接收到的是客户端发来的注册请求,所以代码会进入到 addPublisherIndexes 方法
private final ConcurrentMap<Service, Set<String>> publisherIndexes = new ConcurrentHashMap<>();
private void addPublisherIndexes(Service service, String clientId) {
publisherIndexes.computeIfAbsent(service, key -> new ConcurrentHashSet<>());
publisherIndexes.get(service).add(clientId);
NotifyCenter.publishEvent(new ServiceEvent.ServiceChangedEvent(service, true));
}
- addPublisherIndexes 方法最主要的逻辑是往一个
publisherIndexesMap 里面放入了一个 clientId,然后发布一个服务变更的事件。
MetadataEvent.InstanceMetadataEvent 事件的处理逻辑
InstanceMetadataEvent 会通知到 NamingMetadataManager 类的 onEvent 方法
@Override
public void onEvent(Event event) {
if (event instanceof MetadataEvent.InstanceMetadataEvent) {
handleInstanceMetadataEvent((MetadataEvent.InstanceMetadataEvent) event);
} else if (event instanceof MetadataEvent.ServiceMetadataEvent) {
handleServiceMetadataEvent((MetadataEvent.ServiceMetadataEvent) event);
} else {
handleClientDisconnectEvent((ClientEvent.ClientDisconnectEvent) event);
}
}
onEvent方法中判断了三个事件类型,其中一个便是服务注册发布的是InstanceMetadataEvent类型的事件,所以会进入到 handleInstanceMetadataEvent() 方法
private void handleInstanceMetadataEvent(MetadataEvent.InstanceMetadataEvent event) {
Service service = event.getService();
String metadataId = event.getMetadataId();
if (containInstanceMetadata(service, metadataId)) {
updateExpiredInfo(event.isExpired(),
ExpiredMetadataInfo.newExpiredInstanceMetadata(event.getService(), event.getMetadataId()));
}
}
public boolean containInstanceMetadata(Service service, String metadataId) {
return instanceMetadataMap.containsKey(service) && instanceMetadataMap.get(service).containsKey(metadataId);
}
private void updateExpiredInfo(boolean expired, ExpiredMetadataInfo expiredMetadataInfo) {
if (expired) {
expiredMetadataInfos.add(expiredMetadataInfo);
} else {
expiredMetadataInfos.remove(expiredMetadataInfo);
}
}
- handlerInstanceMetadataEvent 方法首先判断 service 和 metadataId 是否在 instanceMetadataMap 存在,如果存在,则根据事件是否过期,如果已经过期,则将对应的 MetadataInfo 加入到 expiredMetadataInfos 中。
总结
服务端处理服务注册的时序图如下:


浙公网安备 33010602011771号