6、Nacos 服务注册服务端源码分析(五)

上篇分析TaskExecuteEngine以及其两个子类NacosDelayTaskExecuteEngineNacosExecuteTaskExecuteEngine。没看过的小伙伴可以点击这里进行查看。本篇从NacosTask开始分析,并分析后续的逻辑。

Task分析

NacosTask只有一个方法boolean shouldProcess(),即判断是否应该执行。

它有有两个抽象的子类,分别是AbstractExecuteTaskAbstractDelayTask

6、Nacos 服务注册服务端源码分析(五)_第1张图片

AbstractExecuteTask实现很简单,就是为true需要执行。

@Override
public boolean shouldProcess() {
    return true;
}

AbstractDelayTask是延迟执行的,所以它有个延迟机制的判断。

@Override
public boolean shouldProcess() {
    // 如果当前时间减去上次时间还未到达任务执行的间隔时间则返回false
    return (System.currentTimeMillis() - this.lastProcessTime >= this.taskInterval);
}
// 抽象类,由子类判断添加的任务是否可以合并
public abstract void merge(AbstractDelayTask task);

这里还是用上篇分析的PushDelayTask分析下这个方法的作用

@Override
public void merge(AbstractDelayTask task) {
    if (!(task instanceof PushDelayTask)) {
        return;
    }
    PushDelayTask oldTask = (PushDelayTask) task;
    if (isPushToAll() || oldTask.isPushToAll()) {
        // 可以push到所有的的这一类,无需特殊处理
        pushToAll = true;
        targetClients = null;
    } else {
        // 设置个集合,将连接放入,后续要一个个的推,这部分是针对失败的数据
        targetClients.addAll(oldTask.getTargetClients());
    }
    setLastProcessTime(Math.min(getLastProcessTime(), task.getLastProcessTime()));
    Loggers.PUSH.info("[PUSH] Task merge for {}", service);
}

这里也和之前分析的NacosDelayTaskExecuteEngineNacosExecuteTaskExecuteEngine。因为NacosExecuteTaskExecuteEngine无需延迟,所以默认就是需要执行,来一个执行一个。而NacosDelayTaskExecuteEngine则会有个延迟的处理,主要针对失败的,延迟处理。因为既然是失败的,100ms对于失败的处理来说,间隔时间还是短了一些,所以放入到集合中,等延迟时间到了后再统一处理,这里的延迟时间的1s。

下面我们继续上次的代码往后分析

delayTaskEngine.getPushExecutor().doPushWithCallback(each, subscriber, wrapper,
                        new ServicePushCallback(each, subscriber, wrapper.getOriginalData(), delayTask.isPushToAll()));

第一步是获取delayTaskEngine.getPushExecutor(),跟踪这个类分析,发现是由构造方法传入的。再往上跟踪,可以看到该类是归spring托管的。注入的PushExecutorDelegate

这个Delegate单词翻译是委托,说明这个是委托模式。看下这个类的一些成员变量和核心方法。

// rpc执行类,V2版本使用
private final PushExecutorRpcImpl rpcPushExecuteService;
// udp执行类,V1版本使用
private final PushExecutorUdpImpl udpPushExecuteService;

public PushExecutorDelegate(PushExecutorRpcImpl rpcPushExecuteService, PushExecutorUdpImpl udpPushExecuteService) {
    this.rpcPushExecuteService = rpcPushExecuteService;
    this.udpPushExecuteService = udpPushExecuteService;
}

private PushExecutor getPushExecuteService(String clientId, Subscriber subscriber) {
    Optional<SpiPushExecutor> result = SpiImplPushExecutorHolder.getInstance()
        .findPushExecutorSpiImpl(clientId, subscriber);
    if (result.isPresent()) {
        return result.get();
    }
    // use nacos default push executor
    // 根据连接的客户端id识别是由upd推送还是rpc推送
    return clientId.contains(IpPortBasedClient.ID_DELIMITER) ? udpPushExecuteService : rpcPushExecuteService;
}

本篇分析的2.2.0的源码,自然是rpc版本的推送。至于为什么弃用upd,大家可以看下第一篇的 1、Nacos 服务注册客户端源码分析中的引文,这里不做解释了。

我们顺着思路继续分析com.alibaba.nacos.naming.push.v2.executor.PushExecutorRpcImpl#doPushWithCallback

@Override
public void doPushWithCallback(String clientId, Subscriber subscriber, PushDataWrapper data,
                               NamingPushCallback callBack) {
    // 获取服务信息
    ServiceInfo actualServiceInfo = getServiceInfo(data, subscriber);
    callBack.setActualServiceInfo(actualServiceInfo);
    // 构建一个NotifySubscriberRequest,通过grpc向客户端发送信息
    pushService.pushWithCallback(clientId, NotifySubscriberRequest.buildNotifySubscriberRequest(actualServiceInfo),
                                 callBack, GlobalExecutor.getCallbackExecutor());
}

public void pushWithCallback(String connectionId, ServerRequest request, PushCallBack requestCallBack,
                             Executor executor) {
    // 拿到客户端的连接
    Connection connection = connectionManager.getConnection(connectionId);
    if (connection != null) {
        try {
            // 发送异步请求
            connection.asyncRequest(request, new AbstractRequestCallBack(requestCallBack.getTimeout()) {

                @Override
                public Executor getExecutor() {
                    return executor;
                }

                @Override
                public void onResponse(Response response) {
                    if (response.isSuccess()) {
                        requestCallBack.onSuccess();
                    } else {
                        requestCallBack.onFail(new NacosException(response.getErrorCode(), response.getMessage()));
                    }
                }

                @Override
                public void onException(Throwable e) {
                    requestCallBack.onFail(e);
                }
            });
        } catch (ConnectionAlreadyClosedException e) {
            connectionManager.unregister(connectionId);
            requestCallBack.onSuccess();
        } catch (Exception e) {
            Loggers.REMOTE_DIGEST
                .error("error to send push response to connectionId ={},push response={}", connectionId,
                       request, e);
            requestCallBack.onFail(e);
        }
    } else {
        requestCallBack.onSuccess();
    }
}

这里构建了一个NotifySubscriberRequest。回忆一下我们之前分析的代码。一个Request代表着一类网络请求,一定有着一个Handler处理。这里是由com.alibaba.nacos.client.naming.remote.gprc.NamingPushRequestHandler来处理。

可以看到这个包在client包中。也就是grpc的双向流的处理。可以直接接受服务的请求,客户端直接处理。我们简单看下客户端的处理。

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 notifyRequest = (NotifySubscriberRequest) request;
            // 处理逻辑
            serviceInfoHolder.processServiceInfo(notifyRequest.getServiceInfo());
            return new NotifySubscriberResponse();
        }
        return null;
    }
}

public ServiceInfo processServiceInfo(ServiceInfo serviceInfo) {
    String serviceKey = serviceInfo.getKey();
    if (serviceKey == null) {
        return null;
    }
    // 获取老的服务
    ServiceInfo oldService = serviceInfoMap.get(serviceInfo.getKey());
    if (isEmptyOrErrorPush(serviceInfo)) {
        //empty or error push, just ignore
        return oldService;
    }
    // 放入客户端的缓存
    serviceInfoMap.put(serviceInfo.getKey(), serviceInfo);
    // 对比下是否发生改变
    boolean changed = isChangedServiceInfo(oldService, serviceInfo);
    if (StringUtils.isBlank(serviceInfo.getJsonFromServer())) {
        serviceInfo.setJsonFromServer(JacksonUtils.toJson(serviceInfo));
    }
    MetricsMonitor.getServiceInfoMapSizeMonitor().set(serviceInfoMap.size());
    if (changed) {
        NAMING_LOGGER.info("current ips:({}) service: {} -> {}", serviceInfo.ipCount(), serviceInfo.getKey(),
                           JacksonUtils.toJson(serviceInfo.getHosts()));
        // 若改变,发送改变事件
        NotifyCenter.publishEvent(new InstancesChangeEvent(notifierEventScope, serviceInfo.getName(), serviceInfo.getGroupName(),
                                                           serviceInfo.getClusters(), serviceInfo.getHosts()));
        // 磁盘缓存也写一份
        DiskCache.write(serviceInfo, cacheDir);
    }
    return serviceInfo;
}

总结

本篇的内容不多,主要是对上篇的一些扫尾的内容。下一篇我准备将这个注册服务端再整理一遍,毕竟分了几个章节进行讲解,知识点也比较零碎。敬请期待后续的总集篇。

你可能感兴趣的:(Nacos,源码分析,java,分布式,中间件)