4. 服务端处理客户端的服务查询请求流程

客户端查询服务时,会发送两种请求:

  • 第一种:发送订阅服务请求,SubscribeServiceRequest
  • 第二种:发送服务查询请求,ServiceQueryRequest

SubscribeServiceRequest 请求处理

在 Nacos 服务端,SubscribeServiceRequest 请求 SubscribeServiceRequestHandler 的 handle 方法进行处理。

@Override
@Secured(action = ActionTypes.READ)
public SubscribeServiceResponse handle(SubscribeServiceRequest request, RequestMeta meta) throws NacosException {
	String namespaceId = request.getNamespace();
	String serviceName = request.getServiceName();
	String groupName = request.getGroupName();
	String app = request.getHeader("app", "unknown");
	String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
	// 根据 request 的信息生成 Service 实例,用于表示服务实例。
	Service service = Service.newService(namespaceId, groupName, serviceName, true);
	// 根据 request 和 meta 生成 Subscriber 实例,用于表示服务订阅着。
	Subscriber subscriber = new Subscriber(meta.getClientIp(), meta.getClientVersion(), app, meta.getClientIp(),
			namespaceId, groupedServiceName, 0, request.getClusters());
	// 根据 service 实例从 Service 存储器中读取存储的 service 详情信息,得到结果后,封装成 ServiceInfo 对象。
	ServiceInfo serviceInfo = ServiceUtil.selectInstancesWithHealthyProtection(serviceStorage.getData(service),
			metadataManager.getServiceMetadata(service).orElse(null), subscriber.getCluster(), false,
			true, subscriber.getIp());
	
	if (request.isSubscribe()) {
		// 如果请求的 subscribe 字段为 true
		// 向客户端连接对应的 Client 订阅服务
		clientOperationService.subscribeService(service, subscriber, meta.getConnectionId());
		// 发布订阅服务追踪事件
		NotifyCenter.publishEvent(new SubscribeServiceTraceEvent(System.currentTimeMillis(),
				meta.getClientIp(), service.getNamespace(), service.getGroup(), service.getName()));
	} else {
		// 如果 subscribe 字段为 false
		// 向客户端对应的 Client 取消订阅服务
		clientOperationService.unsubscribeService(service, subscriber, meta.getConnectionId());
		// 发布取消订阅服务追踪事件
		NotifyCenter.publishEvent(new UnsubscribeServiceTraceEvent(System.currentTimeMillis(),
				meta.getClientIp(), service.getNamespace(), service.getGroup(), service.getName()));
	}
	// 返回订阅结果
	return new SubscribeServiceResponse(ResponseCode.SUCCESS.getCode(), "success", serviceInfo);
}

SubscribeServiceRequestHandler 的 handle 方法的主要逻辑:

  1. 读取缓存
  2. 添加订阅者,如果被订阅的服务有变动,需要通知订阅者

读取缓存

// 读取缓存
ServiceInfo serviceInfo = ServiceUtil.selectInstancesWithHealthyProtection(serviceStorage.getData(service),
	metadataManager.getServiceMetadata(service).orElse(null), subscriber);
  • 调用 serviceStorage 的 getData 方法,得到结果
  • 再调用 metadataManager 的 getServiceMetadata 方法,得到结果
  • 将上两步得到的结果和订阅者封装成 ServiceInfo

调用 serviceStorage 的 getData 方法

// ServiceStorage
private final ConcurrentMap<Service, ServiceInfo> serviceDataIndexes;

public ServiceInfo getData(Service service) {
    // 判断缓存中是否有数据,如果有直接取缓存数据,缓存没有就调用 getPushData(service) 
    return serviceDataIndexes.containsKey(service) ? serviceDataIndexes.get(service) : getPushData(service);
}

public ServiceInfo getPushData(Service service) {
	// 创建空的 ServiceInfo 对象
	ServiceInfo result = emptyServiceInfo(service);
	if (!ServiceManager.getInstance().containSingleton(service)) {
		return result;
	}
	Service singleton = ServiceManager.getInstance().getSingleton(service);
	// 获取实例列表,hosts 属性就是对应 Instance 数据
	result.setHosts(getAllInstancesFromIndex(singleton));
	// 放入到缓存
	serviceDataIndexes.put(singleton, result);
	return result;
}
  • getData 方法不止是从 serviceDataIndexes 获取数据,还会在获取数据失败后,调用 getPushData 方法将 Service 保存到 serviceDataIndexs 中。getPushData 方法会调用 getAllInstancesFromIndex 方法获取 service 对应的所有示例,一个服务可以有多个实例,每个实例有不同的 host

调用 getAllInstancesFromIndex 方法

private List<Instance> getAllInstancesFromIndex(Service service) {

    Set<Instance> result = new HashSet<>();
    Set<String> clusters = new HashSet<>();
    
    // 通过 service 获取对应 service 全部的 ClientId
    for (String each : serviceIndexesManager.getAllClientsRegisteredService(service)) {
        
        // 遍历每一个 client,通过 clientId 查找注册到 client 的 Instance 信息,
        Optional<InstancePublishInfo> instancePublishInfo = getInstanceInfo(each, service);
        if (instancePublishInfo.isPresent()) {
            // 将获取到的 InstancePublishInfo 转换成 Instance
            Instance instance = parseInstance(service, instancePublishInfo.get());
            // 将 Instance 存到结果集合内
            result.add(instance);
            // 将 Instance 对应的集群信息保存到 clusters 集合内。
            clusters.add(instance.getClusterName());
        }
    }
    // 缓存该 service 对应有哪些 集群
    serviceClusterIndex.put(service, clusters);
    // 返回结果
    return new LinkedList<>(result);
}
  • 获取 service 对应的 client id 列表
  • 遍历 client id 列表,获取每一个 client id 内 serivce 对应的服务实例。
  • 缓存服务和集群信息,并返回 Instance 实例结果列表

获取 service 对应的 client id 列表

调用 ClientServiceIndexesManager 的 getAllClientsRegisteredService 方法找到 service 对应的 client 列表

public Collection<String> getAllClientsRegisteredService(Service service) {
    // 从注册表中获取 clientId 集合
    return publisherIndexes.containsKey(service) ? publisherIndexes.get(service) : new ConcurrentHashSet<>();
}
  • getAllClientsRegisteredService 会从 publisherIndexes 集合中找到 service 对应的 clientId 列表。(服务端在处理客户端的注册请求时,会将 client 和 service 的对应关系存储到 ClientServiceIndexesManager 的 publisherIndex Map 内,publisherIndex Map 的 key 是 service,value 是为 service 所注册的客户端的 ID 列表,一个服务可以被多个客户端注册)

遍历 client id 列表,获取每一个 client id 内 serivce 对应的服务实例

private Optional<InstancePublishInfo> getInstanceInfo(String clientId, Service service) {
    // 通过 clientId 获取每一个对应的 client 连接对象
    Client client = clientManager.getClient(clientId);
    if (null == client) {
        return Optional.empty();
    }
    // 在 client 对象中,获取实例信息
    return Optional.ofNullable(client.getInstancePublishInfo(service));
}
  • 调用 clientManager,获取 clientId 对应的 client
  • 从 client 中查询 service 对应的服务实例并返回。

调用 metadataManager 的 getServiceMetadata 方法

// NamingMetadataManager
public Optional<ServiceMetadata> getServiceMetadata(Service service) {
	return Optional.ofNullable(serviceMetadataMap.get(service));
}
  • getServiceMetadata 主要是从 serviceMetadataMap 中获取对应的服务元数据。(元数据在 serviceStorage 的 getData 方法调用的getSingleton 方法中被添加到 serviceMetadataMap 中,getSingleton 方法会获取不到 Service 后,会发布一个 ServiceMetadataEvent 事件,这个事件会向 serviceMetadataMap 添加服务元数据。)

添加订阅者,如果被订阅的服务有变动,需要通知订阅者

if (request.isSubscribe()) {
	// 添加订阅者,如果被订阅的服务有变动,需要通知订阅者
	clientOperationService.subscribeService(service, subscriber, meta.getConnectionId());
} else {
	clientOperationService.unsubscribeService(service, subscriber, meta.getConnectionId());
}

由于使用的是 grpc 调用,所以应该进入 EphemeralClientOperationServiceImpl 的 subscribeService 完成服务订阅

/**
 * 订阅者列表
 */
protected final ConcurrentHashMap<Service, Subscriber> subscribers = new ConcurrentHashMap<>(16, 0.75f, 1);

@Override
public void subscribeService(Service service, Subscriber subscriber, String clientId) {

    // service 是对应 stock-service
    Service singleton = ServiceManager.getInstance().getSingletonIfExist(service).orElse(service);

    // 这里的 clientId 是 order-service 的 Client 对象
    Client client = clientManager.getClient(clientId);
    if (!clientIsLegal(client, clientId)) {
        return;
    }
    // 服务对应的 Client 对象中,添加订阅者
    client.addServiceSubscriber(singleton, subscriber);
    client.setLastUpdatedTime();
    // 发布客户端订阅事件
    NotifyCenter.publishEvent(new ClientOperationEvent.ClientSubscribeServiceEvent(singleton, clientId));
}

@Override
public boolean addServiceSubscriber(Service service, Subscriber subscriber) {
    // 添加订阅者,key = stock-service 、 value = order-service
    if (null == subscribers.put(service, subscriber)) {
        MetricsMonitor.incrementSubscribeCount();
    }
    return true;
}

  • 添加订阅者的目的是在服务更新的时候,向对应的 client 推送服务,所以在 subscribeService 方法中,将 client 和服务信息保存到 EphemeralClientOperationServiceImpl 的 subscribers 中,并在订阅者添加完毕后,发布 ClientOperationEvent.ClientSubscribeServiceEvent 事件
  • ClientSubscribeServiceEvent 事件最终会被 ClientServiceIndexesManager 的 onEven 方法消费

ClientServiceIndexesManager 的 onEven 方法

@Override
public void onEvent(Event event) {
   if (event instanceof ClientEvent.ClientDisconnectEvent) {
	   handleClientDisconnect((ClientEvent.ClientDisconnectEvent) event);
   } else if (event instanceof ClientOperationEvent) {
	   handleClientOperation((ClientOperationEvent) event); // 消费 ClientOperationEvent 事件
   }
}

private void handleClientOperation(ClientOperationEvent event) {
	Service service = event.getService();
	String clientId = event.getClientId();
	if (event instanceof ClientOperationEvent.ClientRegisterServiceEvent) {
		addPublisherIndexes(service, clientId);
	} else if (event instanceof ClientOperationEvent.ClientDeregisterServiceEvent) {
		removePublisherIndexes(service, clientId);
	} else if (event instanceof ClientOperationEvent.ClientSubscribeServiceEvent) { 
		addSubscriberIndexes(service, clientId); // 消费 ClientOperationEvent.ClientSubscribeServiceEvent 事件
	} else if (event instanceof ClientOperationEvent.ClientUnsubscribeServiceEvent) {
		removeSubscriberIndexes(service, clientId);
	}
}
private final ConcurrentMap<Service, Set<String>> subscriberIndexes = new ConcurrentHashMap<>();

private void addSubscriberIndexes(Service service, String clientId) {
	// 将service 和 clientid 添加到 subscriberIndexes 中
	subscriberIndexes.computeIfAbsent(service, key -> new ConcurrentHashSet<>());
	// Fix #5404, Only first time add need notify event.
	if (subscriberIndexes.get(service).add(clientId)) {
		// 发布服务订阅事件
		NotifyCenter.publishEvent(new ServiceEvent.ServiceSubscribedEvent(service, clientId));
	}
}
  • ClientServiceIndexesManager 的 onEven 方法最终会将 service 和 client 的映射关系保存到 subscriberIndexes 中。其目的是为了后续有服务变动时,即使向订阅者推送消息。
  • 添加映射关系后,会发布 ServiceEvent.ServiceSubscribedEvent 事件。

ServiceQueryRequest 请求处理

在 Nacos 服务端,ServiceQueryRequest 请求 ServiceQueryRequestHandler 的 handle 方法进行处理。ServiceQueryRequestHandler 和 SubscribeServiceRequestHandler 的区别在于,ServiceQueryRequestHandler 只是查询某个服务的详细信息。而 SubscribeServiceRequestHandler 除了查询服务详细信息外,还会在订阅服务变更的消息。

@Override
@Secured(action = ActionTypes.READ)
public QueryServiceResponse handle(ServiceQueryRequest request, RequestMeta meta) throws NacosException {
	String namespaceId = request.getNamespace();
	String groupName = request.getGroupName();
	String serviceName = request.getServiceName();
	Service service = Service.newService(namespaceId, groupName, serviceName);
	String cluster = null == request.getCluster() ? "" : request.getCluster();
	boolean healthyOnly = request.isHealthyOnly();
	ServiceInfo result = serviceStorage.getData(service);
	ServiceMetadata serviceMetadata = metadataManager.getServiceMetadata(service).orElse(null);
	result = ServiceUtil.selectInstancesWithHealthyProtection(result, serviceMetadata, cluster, healthyOnly, true,
			meta.getClientIp());
	return QueryServiceResponse.buildSuccessResponse(result);
}

ServiceQueryRequestHandler 方法的主要逻辑:

  1. 从 request 解析出 namespaceid,groupname, servicename,并根据这些属性创建一个空的 Service
  2. 从 serviceStorage 中获取 service 对应的服务信息 ServiceInfo
  3. 从 matadataManager 中获取服务的元数据 ServiceMetadata
  4. 根据获取到的 ServiceInfo 和 ServiceMetadata 创建一个 ServiceInfo 对象。
  5. 将这个 ServiceInfo 对象返回给客户端。
posted @ 2024-11-13 14:15  Jacob-Chen  阅读(74)  评论(0)    收藏  举报