5. 服务变动通知客户端流程

服务注册和订阅逻辑

服务注册逻辑

在客户端向 Nacos 服务端发起注册时,服务端会发布 ClientRegisterServiceEvent 事件,ClientRegisterServiceEvent 会通知到 ClientServiceIndexesManager 的 onEvent 方法,该方法最终会调用 ClientServiceIndexesManager 的 addPublisherIndexes 方法

// ClientServiceIndexesManager
private void addPublisherIndexes(Service service, String clientId) {
    // 把 clientId 写入注册表
    publisherIndexes.computeIfAbsent(service, (key) -> new ConcurrentHashSet<>());
    publisherIndexes.get(service).add(clientId);
    // 发布服务改变事件
    NotifyCenter.publishEvent(new ServiceEvent.ServiceChangedEvent(service, true));
}
  • addPublisherIndexes 最终会将 service 和 clientId 加入到 publisherIndexes。
  • 在添加注册表完成后,会发布 ServiceEvent.ServiceChangedEvent 事件。

服务订阅逻辑

在客户端发起服务实例查询时,会调用订阅接口,服务端在处理订阅请求时,会发布 ClientSubscribeServiceEvent 事件,ClientSubscribeServiceEvent 事件最终也是由 ClientServiceIndexesManager 的 onEvent 方法处理,ClientServiceIndexesManager 的 onEvent 方法最终会调用 ClientServiceIndexesManager 的 addSubscriberIndexes 方法

// ClientServiceIndexesManager
private void addSubscriberIndexes(Service service, String clientId) {
    // 判断订阅表是否存在该 service
    subscriberIndexes.computeIfAbsent(service, (key) -> new ConcurrentHashSet<>());
    // Fix #5404, 添加订阅表,只有第一次添加需要通知事件
    if (subscriberIndexes.get(service).add(clientId)) {
        NotifyCenter.publishEvent(new ServiceEvent.ServiceSubscribedEvent(service, clientId));
    }
}
  • addSubscriberIndexes 最终会将 service 和 clientId 加入到 subscriberIndexes。
  • 在添加注册表完成后,会发布 ServiceEvent.ServiceSubscribedEvent 事件。

事件处理

以上发布的 ServiceEvent.ServiceChangedEvent 事件和 ServiceEvent.ServiceSubscribedEvent 事件,都会交由 NamingSubscriberServiceV2Impl 的 onEvent() 方法处理

// NamingSubscriberServiceV2Impl
private final PushDelayTaskExecuteEngine delayTaskEngine;

public NamingSubscriberServiceV2Impl(ClientManagerDelegate clientManager,
		ClientServiceIndexesManager indexesManager, ServiceStorage serviceStorage,
		NamingMetadataManager metadataManager, PushExecutorDelegate pushExecutor, SwitchDomain switchDomain) {
	this.clientManager = clientManager;
	this.indexesManager = indexesManager;
	this.delayTaskEngine = new PushDelayTaskExecuteEngine(clientManager, indexesManager, serviceStorage,
			metadataManager, pushExecutor, switchDomain);
	NotifyCenter.registerSubscriber(this, NamingEventPublisherFactory.getInstance());
}

@Override
public void onEvent(Event event) {
	if (event instanceof ServiceEvent.ServiceChangedEvent) {
		// If service changed, push to all subscribers.
		ServiceEvent.ServiceChangedEvent serviceChangedEvent = (ServiceEvent.ServiceChangedEvent) event;
		Service service = serviceChangedEvent.getService();
		delayTaskEngine.addTask(service, new PushDelayTask(service, PushConfig.getInstance().getPushTaskDelay()));
		MetricsMonitor.incrementServiceChangeCount(service.getNamespace(), service.getGroup(), service.getName());
	} else if (event instanceof ServiceEvent.ServiceSubscribedEvent) {
		// If service is subscribed by one client, only push this client.
		ServiceEvent.ServiceSubscribedEvent subscribedEvent = (ServiceEvent.ServiceSubscribedEvent) event;
		Service service = subscribedEvent.getService();
		delayTaskEngine.addTask(service, new PushDelayTask(service, PushConfig.getInstance().getPushTaskDelay(),
				subscribedEvent.getClientId()));
	}
}
  • onEvent 方法会从事件中获取 service ,然后调用 delayTaskEngine 提交一个 PushDelayTask 任务,delayTaskEngine 的实现类为 PushDelayTaskExecuteEngine,PushDelayTaskExecuteEngine 对象在 NamingSubscriberServiceV2Impl 构造方法中被创建

PushDelayTaskExecuteEngine

PushDelayTaskExecuteEngine 类结构

image

PushDelayTaskExecuteEngine 类介绍

PushDelayTaskExecuteEngine 是推送延迟任务执行引擎,顾名思义是执行延迟任务,可以往执行引擎中添加任务,然后该任务会被延时执行。Nacos 定义了 NacosTaskProcessor 任务处理器接口,NacosTaskProcessor 有很多实现类,PushDelayTaskExecuteEngine 的父类AbstractNacosTaskExecuteEngine 会存储和调度这些任务处理器实现类,相当于这些任务处理器的执行引擎。

public abstract class AbstractNacosTaskExecuteEngine<T extends NacosTask> implements NacosTaskExecuteEngine<T> {
    
    private final ConcurrentHashMap<Object, NacosTaskProcessor> taskProcessors = new ConcurrentHashMap<>();
    
    private NacosTaskProcessor defaultTaskProcessor;
    
    @Override
    public void addProcessor(Object key, NacosTaskProcessor taskProcessor) {
        taskProcessors.putIfAbsent(key, taskProcessor);
    }
    
    @Override
    public void setDefaultTaskProcessor(NacosTaskProcessor defaultTaskProcessor) {
        this.defaultTaskProcessor = defaultTaskProcessor;
    }
    
}

在外部可以通过 addProcessorsetDefaultTaskProcessor 方法,来往执行引擎中心添加处理器。

在添加完处理器后,PushDelayTaskExecuteEngine 的父类 NacosDelayTaskExecuteEngine 会使用线程池启动一个 ProcessRunnable 任务,执行 AbstractNacosTaskExecuteEngine 保存的 NacosTaskProcessor

public NacosDelayTaskExecuteEngine(String name, int initCapacity, Logger logger, long processInterval) {
    super(logger);
    tasks = new ConcurrentHashMap<>(initCapacity);
    processingExecutor = ExecutorFactory.newSingleScheduledExecutorService(new NameThreadFactory(name));
    // 启动 ProcessRunnable 线程任务
    processingExecutor
            .scheduleWithFixedDelay(new ProcessRunnable(), processInterval, processInterval, TimeUnit.MILLISECONDS);
}

ProcessRunnable 类

private class ProcessRunnable implements Runnable {
    
    @Override
    public void run() {
        try {
            processTasks();
        } catch (Throwable e) {
            getEngineLog().error(e.toString(), e);
        }
    }
}

ProcessRunnable 实现了 Runnable 接口,所以线程执行时,会调用其 run() 方法。run 会调用了 processTasks()方法,在这个方法中就有很多的逻辑

// 任务池
protected final ConcurrentHashMap<Object, AbstractDelayTask> tasks;

/**
 * process tasks in execute engine.
 */
protected void processTasks() {
    // 获取全部的任务,进行遍历
    Collection<Object> keys = getAllTaskKeys();
    for (Object taskKey : keys) {
        
        // 通过任务 key,将任务从 tasks Map 中删除,先删除任务,再执行任务内容
        AbstractDelayTask task = removeTask(taskKey);
        if (null == task) {
            continue;
        }
        // 通过任务 key 获取对应的任务处理器,
        NacosTaskProcessor processor = getProcessor(taskKey);
        if (null == processor) {
            getEngineLog().error("processor not found for task, so discarded. " + task);
            continue;
        }
        try {
            // 调用处理器的处理方法
            if (!processor.process(task)) {
                retryFailedTask(taskKey, task);
            }
        } catch (Throwable e) {
            getEngineLog().error("Nacos task execute error ", e);
            retryFailedTask(taskKey, task);
        }
    }
}
  • 首先会有一个 Map 类型的任务池 tasks ,从任务池中获取全部的任务,进行遍历。通过 taskKey 获取到具体的任务之后,再通过 taskKey 获取对应的处理器,最终调用处理器的处理方法。

获取处理器的方法

// AbstractNacosTaskExecuteEngine
@Override
public NacosTaskProcessor getProcessor(Object key) {
 // 如果处理器中没有对应的 key,那么就返回默认的任务处理器
 return taskProcessors.containsKey(key) ? taskProcessors.get(key) : defaultTaskProcessor;
}
  • getProcessor() 方法从 taskProcessors Map 中获取对应的 NacosTaskProcessor 实现类,如果 taskProcessors 中没有,则会直接返回默认的 defaultTaskProcessor

tasks 任务则在 NacosDelayTaskExecuteEngine 的 addTask() 方法中被添加

@Override
public void addTask(Object key, AbstractDelayTask newTask) {
    lock.lock();
    try {
        // 往任务池添加任务
        AbstractDelayTask existTask = tasks.get(key);
        if (null != existTask) {
            newTask.merge(existTask);
        }
        tasks.put(key, newTask);
    } finally {
        lock.unlock();
    }
}
  • addTask 首先从 tasks 中获取 key 对应的任务,如果任务存在,在和 newTask 进行合并,合并完毕后,再将任务添加到 tasks 中。

服务变动/订阅事件处理

从服务注册和订阅发布事件后,会通知 NamingSubscriberServiceV2Impl 的 onEvent() 方法,而 onEvent() 方法则会调用 addTask 方法向 tasks 添加任务。

@Override
public void onEvent(Event event) {
    if (!upgradeJudgement.isUseGrpcFeatures()) {
        return;
    }
    if (event instanceof ServiceEvent.ServiceChangedEvent) {
        // 如果服务变动,会通知该 service 所有的订阅者,更新本地缓存
        ServiceEvent.ServiceChangedEvent serviceChangedEvent = (ServiceEvent.ServiceChangedEvent) event;
        Service service = serviceChangedEvent.getService();
        // 添加任务到 tasks
        delayTaskEngine.addTask(service, new PushDelayTask(service, PushConfig.getInstance().getPushTaskDelay()));
    } else if (event instanceof ServiceEvent.ServiceSubscribedEvent) {
        // 如果服务被一个客户端订阅,则只推送该客户端
        ServiceEvent.ServiceSubscribedEvent subscribedEvent = (ServiceEvent.ServiceSubscribedEvent) event;
        Service service = subscribedEvent.getService();
        // 添加任务到 tasks
        delayTaskEngine.addTask(service, new PushDelayTask(service, PushConfig.getInstance().getPushTaskDelay(),
                subscribedEvent.getClientId()));
    }
}
  • 在添加任务的时候,添加的是 PushDelayTask 类型的任务,PushDelayTask 类型的任务会交由 NacosTaskProcessor.PushDelayTaskProcessor 处理器进行处理。NacosTaskProcessor.PushDelayTaskProcessorPushDelayTaskExecuteEngine 初始化时,被设置为 defaultTaskProcessor

根据 AbstractNacosTaskExecuteEngine.getProcessor() 可知,在 taskProcessors 不存在 key 对应的 processor 时,会使用 defaultTaskProcessor,再根据 NacosDelayTaskExecuteEngine.addTask() 可知,向 taskProcessors 查询 processor 使用的 key 为 service,而 taskProcessors 不存在 service 对应的 taskProcessor,所以会使用 defaultTaskProcessor

// PushDelayTaskExecuteEngine
public PushDelayTaskExecuteEngine(ClientManager clientManager, ClientServiceIndexesManager indexesManager,
	  ServiceStorage serviceStorage, NamingMetadataManager metadataManager,
	  PushExecutor pushExecutor, SwitchDomain switchDomain) {
	super(PushDelayTaskExecuteEngine.class.getSimpleName(), Loggers.PUSH);
	this.clientManager = clientManager;
	this.indexesManager = indexesManager;
	this.serviceStorage = serviceStorage;
	this.metadataManager = metadataManager;
	this.pushExecutor = pushExecutor;
	this.switchDomain = switchDomain;
	setDefaultTaskProcessor(new PushDelayTaskProcessor(this));
}

PushDelayTaskProcessor 的 process 方法

@Override
public boolean process(NacosTask task) {
    // 任务类型转换
    PushDelayTask pushDelayTask = (PushDelayTask) task;
    Service service = pushDelayTask.getService();
    // 提交 PushExecuteTask 线程任务
    NamingExecuteTaskDispatcher.getInstance()
            .dispatchAndExecuteTask(service, new PushExecuteTask(service, executeEngine, pushDelayTask));
    return true;
}
  • process 方法首先从 task 中获取 service,然后向 NamingExecuteTaskDispatcher 添加一个 PushExecuteTask 线程任务

PushExecuteTask 的 run 方法

@Override
public void run() {
    try {
        // 从注册表获取当前 service 最新的实例列表数据
        PushDataWrapper wrapper = generatePushData();
        ClientManager clientManager = delayTaskEngine.getClientManager();
        // 获取客户端id,也就是 rpc 客户端连接 id
        for (String each : getTargetClientIds()) {
            // 获取客户端,通知服务变动
            Client client = clientManager.getClient(each);
            if (null == client) {
                // means this client has disconnect
                continue;
            }
            // 获取客户端的订阅者
            Subscriber subscriber = client.getSubscriber(service);
            // 回调客户端
            delayTaskEngine.getPushExecutor().doPushWithCallback(each, subscriber, wrapper,
                    new NamingPushCallback(each, subscriber, wrapper.getOriginalData(), delayTask.isPushToAll()));
        }
    } catch (Exception e) {
        Loggers.PUSH.error("Push task for service" + service.getGroupedServiceName() + " execute failed ", e);
        delayTaskEngine.addTask(service, new PushDelayTask(service, 1000L));
    }
}

private Collection<String> getTargetClientIds() {
    // 通过 pushToAll 这个参数来控制是否推送给全部的 client
    // 如果为 true 推送 service 全部的 client,如果为 false,需要指定 targetClients 参数,只推送该参数里面的 client
    return delayTask.isPushToAll() ? delayTaskEngine.getIndexesManager().getAllClientsSubscribeService(service)
            : delayTask.getTargetClients();
}


public Collection<String> getAllClientsSubscribeService(Service service) {
    // 从订阅表获取 clientId
    return subscriberIndexes.containsKey(service) ? subscriberIndexes.get(service) : new ConcurrentHashSet<>();
}

  • PushExecuteTask 的 run 方法首先判断是否需要推送给所有订阅者,如果不需要,则根据 targetClients 推送指定的 client
  • 然后再从 client 的 subscriberIndexes 找到 service 对应的 subscriber ,然后调用 pushExecutor 的 doPushWithCallBakc() 方法调用客户端的回调方法。

PushExecuteTask 的 isPushToAll 方法

NamingSubscriberServiceV2Impl 在处理服务注册和服务订阅时,会创建不同的 PushDelayTask 方法,服务变动时, pushToAll 会被设置为 ture,表示需要推送给所有订阅客户端,在服务订阅时,pushToAll 为 false,则只会推送给指定的 tagetClients

// PushDelayTask
private Set<String> targetClients;

//  服务变动事件,所使用的构造方法
public PushDelayTask(Service service, long delay) {
    this.service = service;
    pushToAll = true;
    targetClients = null;
    setTaskInterval(delay);
    setLastProcessTime(System.currentTimeMillis());
}

// 服务订阅事件使用的构造方法
public PushDelayTask(Service service, long delay, String targetClient) {
    this.service = service;
    this.pushToAll = false;
    this.targetClients = new HashSet<>(1);
    // 把 clientId 添加到 targetClients 当中,这个 clientId 就是发起服务订阅的客户端ID
    this.targetClients.add(targetClient);
    setTaskInterval(delay);
    setLastProcessTime(System.currentTimeMillis());
}

doPushWithCallback 方法

@Override
public void doPushWithCallback(String clientId, Subscriber subscriber, PushDataWrapper data, PushCallBack callBack) {
    // 这里构建的是 NotifySubscriberRequest 类型请求参数
    pushService.pushWithCallback(clientId, NotifySubscriberRequest.buildNotifySubscriberRequest(getServiceInfo(data, subscriber)),
            callBack, GlobalExecutor.getCallbackExecutor());
}
  • pushService 会向客户端发送一个 NotifySubscriberRequest,NotifySubscriberRequest 会客户端的在 NamingPushRequestHandler 中被处理
// NamingPushRequestHandler 
public class NamingPushRequestHandler implements ServerRequestHandler {
    
    private final ServiceInfoHolder serviceInfoHolder;
    
    public NamingPushRequestHandler(ServiceInfoHolder serviceInfoHolder) {
        this.serviceInfoHolder = serviceInfoHolder;
    }
    
    @Override
    public Response requestReply(Request request) {
        if (request instanceof NotifySubscriberRequest) {
            // 类型转换
            NotifySubscriberRequest notifyResponse = (NotifySubscriberRequest) request;
            // 更新本地缓存数据
            serviceInfoHolder.processServiceInfo(notifyResponse.getServiceInfo());
            return new NotifySubscriberResponse();
        }
        return null;
    }
}
  • 客户端收到 NotifySubscriberRequest 后,会更新本地 serviceInfo 缓存。
posted @ 2024-11-13 14:16  Jacob-Chen  阅读(100)  评论(0)    收藏  举报