6. 服务实例信息同步集群流程

Nacos 服务端处理服务注册的时候,会发布一个 ClientEvent.ClientChangedEvent 事件,其调用链为:
InstanceRequestHandler.handle() -> InstanceRequestHandler.registerInstance() -> EphemeralClientOperationServiceImpl.registerInstance() -> AbstractClient.addServiceInstance()

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;
}

ClientChangedEvent 事件处理

ClientChangedEvent 事件会被 DistroClientDataProcessor 的 onEvent() 方法处理

@Override
public void onEvent(Event event) {
	if (EnvUtil.getStandaloneMode()) {
		return;
	}
	if (event instanceof ClientEvent.ClientVerifyFailedEvent) {
		syncToVerifyFailedServer((ClientEvent.ClientVerifyFailedEvent) event);
	} else {
		syncToAllServer((ClientEvent) event); // 发布的是 ClientChangedEvent事件,有 syncToAllServer() 方法处理
	}
}

private void syncToAllServer(ClientEvent event) {
	Client client = event.getClient();
	// Only ephemeral data sync by Distro, persist client should sync by raft.
	if (null == client || !client.isEphemeral() || !clientManager.isResponsibleClient(client)) {
		return;
	}
	// 客户端注销集群节点同步事件
	if (event instanceof ClientEvent.ClientDisconnectEvent) {
		DistroKey distroKey = new DistroKey(client.getClientId(), TYPE);
		distroProtocol.sync(distroKey, DataOperation.DELETE);
	} else if (event instanceof ClientEvent.ClientChangedEvent) {
		DistroKey distroKey = new DistroKey(client.getClientId(), TYPE);
		distroProtocol.sync(distroKey, DataOperation.CHANGE); // 发布的是 ClientChangedEvent 事件,调用 distroProtocol.sync() 方法
	}
}

DistroProtocol 的 sync 方法

public void sync(DistroKey distroKey, DataOperation action) {
	sync(distroKey, action, DistroConfig.getInstance().getSyncDelayMillis());
}
public void sync(DistroKey distroKey, DataOperation action, long delay) {
	for (Member each : memberManager.allMembersWithoutSelf()) {
		syncToTarget(distroKey, action, each.getAddress(), delay);
	}
}
public void syncToTarget(DistroKey distroKey, DataOperation action, String targetServer, long delay) {  
    DistroKey distroKeyWithTarget = new DistroKey(distroKey.getResourceKey(), distroKey.getResourceType(),  
            targetServer);  
    DistroDelayTask distroDelayTask = new DistroDelayTask(distroKeyWithTarget, action, delay);  
    distroTaskEngineHolder.getDelayTaskExecuteEngine().addTask(distroKeyWithTarget, distroDelayTask);  
    if (Loggers.DISTRO.isDebugEnabled()) {  
        Loggers.DISTRO.debug("[DISTRO-SCHEDULE] {} to {}", distroKey, targetServer);  
    }  
}
  • syncToTarget 方法会向 DistroTaskEngineHolderDistroDelayTaskExecuteEngine 添加一个 DistroDelayTaskDistroDelayTaskExecuteEngine 类似于服务变更通知时,使用的 PushDelayTaskExecuteEngineDistroDelayTask 则对应与服务变更通知的 PushDelayTask

DistroTaskEngineHolder 在构造函数中,会向 DistroDelayTaskExecuteEngine 设置默认的 taskProcessor

public DistroTaskEngineHolder(DistroComponentHolder distroComponentHolder) {
	DistroDelayTaskProcessor defaultDelayTaskProcessor = new DistroDelayTaskProcessor(this, distroComponentHolder);
	delayTaskExecuteEngine.setDefaultTaskProcessor(defaultDelayTaskProcessor);
}
  • DistroTaskEngineHolder 将 delayTaskExecuteEngine 的默认处理器设置为 DistroDelayTaskProcessor

DistroDelayTaskExecuteEngine

DistroDelayTaskExecuteEngine 类结构

image

DistroDelayTaskExecuteEngine 和 PushDelayTaskExecuteEngine 一样,都继承自 NacosDelayTaskExecuteEngine

DistroDelayTaskExecuteEngine 类介绍

DistroDelayTaskExecuteEngine 的处理任务的逻辑与 PushDelayTaskExecuteEngine 的一样,会调度 DistroDelayTaskProcessor 处理 DistroDelayTask

// DistroDelayTaskProcessor
@Override
public boolean process(NacosTask task) {
    if (!(task instanceof DistroDelayTask)) {
        return true;
    }
    DistroDelayTask distroDelayTask = (DistroDelayTask) task;
    DistroKey distroKey = distroDelayTask.getDistroKey();
    switch (distroDelayTask.getAction()) {
        case DELETE:
            // 删除任务
            DistroSyncDeleteTask syncDeleteTask = new DistroSyncDeleteTask(distroKey, distroComponentHolder);
            distroTaskEngineHolder.getExecuteWorkersManager().addTask(distroKey, syncDeleteTask);
            return true;
        case CHANGE:
        case ADD:
            // 改变、新增都会走这一段逻辑
            DistroSyncChangeTask syncChangeTask = new DistroSyncChangeTask(distroKey, distroComponentHolder);
            distroTaskEngineHolder.getExecuteWorkersManager().addTask(distroKey, syncChangeTask);
            return true;
        default:
            return false;
    }
}
  • 不管是删除,还是改变和新增的操作,都是创建了一个任务(DistroSyncChangeTask 或者 DistroSyncDeleteTask),添加到了 DistroTaskEngineHolder 的 DistroExecuteTaskExecuteEngine 中。

DistroSyncChangeTask 的 run 方法

@Override
public void run() {

    // Nacos:Naming:v2:ClientData 
    String type = getDistroKey().getResourceType();
    
    // 获取的对象是 DistroClientTransportAgent
    DistroTransportAgent transportAgent = distroComponentHolder.findTransportAgent(type);
    if (null == transportAgent) {
        Loggers.DISTRO.warn("No found transport agent for type [{}]", type);
        return;
    }
    Loggers.DISTRO.info("[DISTRO-START] {}", toString());
    
    // 默认返回 true
    if (transportAgent.supportCallbackTransport()) {
        // 走这个方法
        doExecuteWithCallback(new DistroExecuteCallback());
    } else {
        executeDistroTask();
    }
}

  • 首先从 distroKey 中获取 ResourceType,默认值为 Nacos:Naming:v2:ClientData
  • 根据 type 找到对应的 DistroTransportAgent 后,调用 supportCallbackTransport(),判断是否走回调机制,如果走回调机制,在执行完任务后,会回调 DistroExecuteCallbackonSuccess 或者 onFailed 方法

DistroSyncChangeTask 的 doExecuteWithCallback 方法

@Override
protected void doExecuteWithCallback(DistroCallback callback) {
    
    String type = getDistroKey().getResourceType();
    // 获取请求数据
    DistroData distroData = getDistroData(type);
    if (null == distroData) {
        Loggers.DISTRO.warn("[DISTRO] {} with null data to sync, skip", toString());
        return;
    }
    // syncData 同步集群节点
    getDistroComponentHolder().findTransportAgent(type)
            .syncData(distroData, getDistroKey().getTargetServer(), callback);
}
  • doExecuteWithCallback 方法会调用 DistroTransportAgent 的 syncData 向目标服务器同步数据。

DistroTransportAgent 的 syncData 方法

@Override
public void syncData(DistroData data, String targetServer, DistroCallback callback) {
    if (isNoExistTarget(targetServer)) {
        callback.onSuccess();
        return;
    }
    // 创建请求对象
    DistroDataRequest request = new DistroDataRequest(data, data.getType());
    
    // 找到集群节点
    Member member = memberManager.find(targetServer);
    try {
        // 发送 rpc 异步请求
        clusterRpcClientProxy.asyncRequest(member, request, new DistroRpcCallbackWrapper(callback, member));
    } catch (NacosException nacosException) {
        callback.onFailed(nacosException);
    }

  • syncData 方法会创建一个 DistroDataRequest,然后使用集群的 rpc client 向其他 Nacos 服务端发送服务信息同步请求。

小结

Nacos 服务端处理服务注册的时候,会发布 ClientChangedEvent 事件,ClientChangedEvent 事件在被处理的过程中,会调用 DistroProtocol 的 sync 方法,sync 方法的主要逻辑:

  1. 先是创建了 DistroDelayTask 延迟任务,放入到了延迟任务执行引擎,由 DistroDelayTaskProcessor 处理器来处理。
  2. 在 DistroDelayTaskProcessor 处理器,处理的逻辑中又创建了 DistroSyncChangeTask 线程任务,执行引擎是先调用了 AbstractDistroExecuteTask 父类中的 run 方法,在 run 方法中又调用了子类的doExecuteWithCallback 方法。
  3. doExecuteWithCallback 方法中会去获取最新的微服务实例列表,通过 rpc 发起异步请求进行数据同步。

DistroDataRequest 的处理逻辑

DistroDataRequest 请求最终会被其他 Nacos 服务端的 DistroDataRequestHandler.handle 方法

// DistroDataRequestHandler
@Override
public DistroDataResponse handle(DistroDataRequest request, RequestMeta meta) throws NacosException {
    try {
        switch (request.getDataOperation()) {
            case VERIFY:
                return handleVerify(request.getDistroData(), meta);
            case SNAPSHOT:
                return handleSnapshot();
            case ADD:
            case CHANGE:
            case DELETE:
                // 不管是添加、改变、删除都是走这一个方法
                return handleSyncData(request.getDistroData());
            case QUERY:
                return handleQueryData(request.getDistroData());
            default:
                return new DistroDataResponse();
        }
    } catch (Exception e) {
        Loggers.DISTRO.error("[DISTRO-FAILED] distro handle with exception", e);
        DistroDataResponse result = new DistroDataResponse();
        result.setErrorCode(ResponseCode.FAIL.getCode());
        result.setMessage("handle distro request with exception");
        return result;
    }
}

  • 无论接收到的同步请求是添加、改变、删除都是走的 handleSyncData() 方法。
// DistroDataRequestHandler
private DistroDataResponse handleSyncData(DistroData distroData) {
    DistroDataResponse result = new DistroDataResponse();
    // onReceive 处理的方法
    if (!distroProtocol.onReceive(distroData)) {
        result.setErrorCode(ResponseCode.FAIL.getCode());
        result.setMessage("[DISTRO-FAILED] distro data handle failed");
    }
    return result;
}

// DistroProtocol 处理的逻辑
public boolean onReceive(DistroData distroData) {
    Loggers.DISTRO.info("[DISTRO] Receive distro data type: {}, key: {}", distroData.getType(),
            distroData.getDistroKey());
    // 还是那个 ResourceType ,Nacos:Naming:v2:ClientData        
    String resourceType = distroData.getDistroKey().getResourceType();
    
    // 获取的是 DistroClientTransportAgent 处理对象
    DistroDataProcessor dataProcessor = distroComponentHolder.findDataProcessor(resourceType);
    if (null == dataProcessor) {
        Loggers.DISTRO.warn("[DISTRO] Can't find data process for received data {}", resourceType);
        return false;
    }
    // 最后调用这个方法 
    return dataProcessor.processData(distroData);
}
  • handleSyncData 方法首先获取 ResourceType,这里的 ResourceType 还是对应 Nacos:Naming:v2:ClientData,通过 type 调用了 findDataProcessor(),这里面是有一个 Map ,获取出来的对象是:DistroClientTransportAgent,最后调用了 processData()
// DistroClientDataProcessor
@Override
public boolean processData(DistroData distroData) {
    switch (distroData.getType()) {
        case ADD:
        case CHANGE:
            // 添加和改变走这个逻辑
            ClientSyncData clientSyncData = ApplicationUtils.getBean(Serializer.class)
                    .deserialize(distroData.getContent(), ClientSyncData.class);
            handlerClientSyncData(clientSyncData);
            return true;
        case DELETE:
            // 删除走这个逻辑
            String deleteClientId = distroData.getDistroKey().getResourceKey();
            Loggers.DISTRO.info("[Client-Delete] Received distro client sync data {}", deleteClientId);
            clientManager.clientDisconnected(deleteClientId);
            return true;
        default:
            return false;
    }
}

  • distroData 是添加、改变类型是,会调用的是这个 handlerClientSyncData() 方法
// DistroClientDataProcessor
private void handlerClientSyncData(ClientSyncData clientSyncData) {
	Loggers.DISTRO
			.info("[Client-Add] Received distro client sync data {}, revision={}", clientSyncData.getClientId(),
					clientSyncData.getAttributes().getClientAttribute(ClientConstants.REVISION, 0L));
	clientManager.syncClientConnected(clientSyncData.getClientId(), clientSyncData.getAttributes());
	Client client = clientManager.getClient(clientSyncData.getClientId());
	upgradeClient(client, clientSyncData);
}
  • andlerClientSyncData() 方法又调用了 upgradeClient()
private void upgradeClient(Client client, ClientSyncData clientSyncData) {
    // 获取同步数据中的命名空间列表
    List<String> namespaces = clientSyncData.getNamespaces();
    // 获取同步数据中的组名列表
    List<String> groupNames = clientSyncData.getGroupNames();
    // 获取同步数据中的服务名列表
    List<String> serviceNames = clientSyncData.getServiceNames();
    // 获取同步数据中的实例发布信息列表
    List<InstancePublishInfo> instances = clientSyncData.getInstancePublishInfos();
    // 创建一个用于存储已同步服务的 HashSet
    Set<Service> syncedService = new HashSet<>();

    // 遍历命名空间、组名和服务名列表,创建服务对象并处理同步
    for (int i = 0; i < namespaces.size(); i++) {
        // 根据命名空间、组名和服务名创建服务对象
        Service service = Service.newService(namespaces.get(i), groupNames.get(i), serviceNames.get(i));
        // 获取服务管理器中的单例服务对象
        Service singleton = ServiceManager.getInstance().getSingleton(service);
        // 将单例服务对象添加到已同步服务集合中
        syncedService.add(singleton);
        // 获取当前索引对应的实例发布信息
        InstancePublishInfo instancePublishInfo = instances.get(i);
        // 如果当前实例发布信息与客户端中对应服务的实例发布信息不相等
        if (!instancePublishInfo.equals(client.getInstancePublishInfo(singleton))) {
            // 向客户端添加服务实例,并发布服务注册事件
            client.addServiceInstance(singleton, instancePublishInfo);
            NotifyCenter.publishEvent(
                    new ClientOperationEvent.ClientRegisterServiceEvent(singleton, client.getClientId()));
        }
    }

    // 遍历客户端已发布的所有服务
    for (Service each : client.getAllPublishedService()) {
        // 如果当前服务不在已同步服务集合中
        if (!syncedService.contains(each)) {
            // 从客户端移除服务实例,并发布服务注销事件
            client.removeServiceInstance(each);
            NotifyCenter.publishEvent(
                    new ClientOperationEvent.ClientDeregisterServiceEvent(each, client.getClientId()));
        }
    }
}
  • upgradeClient 的逻辑和服务端处理服务注册请求的逻辑类似,主要是调用 client 的 addServiceInstance 方法将服务和实例关系注册到 client 中。然后发布 ClientOperationEvent.ClientRegisterServiceEvent 事件

如果同步信息是 DELETE 类型,则会调用 clientManager.clientDisconnected 方法

// EphemeralIpPortClientManager
@Override
public boolean clientDisconnected(String clientId) {
    Loggers.SRV_LOG.info("Client connection {} disconnect, remove instances and subscribers", clientId);
    // 移除客户端信息
    IpPortBasedClient client = clients.remove(clientId);
    if (null == client) {
        return true;
    }
    // 发布客户端注销事件
    NotifyCenter.publishEvent(new ClientEvent.ClientDisconnectEvent(client));
    client.release();
    return true;
}

clientDisconnected 方法在移除客户端信息后,会发布 ClientEvent.ClientDisconnectEvent 事件,ClientEvent.ClientDisconnectEvent 事件会被 DistroClientDataProcessor 和 ClientServiceIndexesManager 处理,使用 DistroClientDataProcessor 是为了同步断连事件到其他的服务器,使用 ClientServiceIndexesManager 则是在则是在本服务器执行客户端注销的相关逻辑。

// ClientServiceIndexesManager 
@Override
public void onEvent(Event event) {
    if (event instanceof ClientEvent.ClientDisconnectEvent) {
        // 处理客户端注销
        handleClientDisconnect((ClientEvent.ClientDisconnectEvent) event);
    } else if (event instanceof ClientOperationEvent) {
        handleClientOperation((ClientOperationEvent) event);
    }
}

private void handleClientDisconnect(ClientEvent.ClientDisconnectEvent event) {
    Client client = event.getClient();
    for (Service each : client.getAllSubscribeService()) {
        // 移除订阅者信息
        removeSubscriberIndexes(each, client.getClientId());
    }
    for (Service each : client.getAllPublishedService()) {
        // 移除注册表信息
        removePublisherIndexes(each, client.getClientId());
    }
}

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