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 方法主要分为一下几个步骤:

  1. 检查 instance 是否合法
  2. 根据传递的 service 参数获取一个新的 service,命名为 singleton
  3. 从 clientManager 中获取 client id 对应的 client。
  4. 向 client 找那个添加服务实例。
  5. 发布相应的事件。

检查 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 方法最主要的逻辑是往一个 publisherIndexes Map 里面放入了一个 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 中。

总结

服务端处理服务注册的时序图如下:
image

posted @ 2024-11-13 14:06  Jacob-Chen  阅读(70)  评论(0)    收藏  举报