5、Nacos 服务注册服务端源码分析(四)

上篇分析事件的订阅者,讲解了几个重要事件的关系。本篇从ServiceEvent.ServiceChangedEvent全面分析后续的处理逻辑。

通过调用分析,我们可以看到NamingSubscriberServiceV2Impl处理的这个事件,其他都是发布事件。

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

看下这个onEvent(Event event)方法。

@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();
        // 将service信息包装成一个PushDelayTask,加入到任务殷勤
        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()));
    }
}

这里有两个很重要的概念PushDelayTaskExecuteEnginePushDelayTask。我们先分析下这两个类。等分析完这两个类后,再看后续逻辑就很简单了。对于优秀的设计,我们花多点时间去消化和理解是很重要的。

PushDelayTaskExecuteEngine

我们先看下PushDelayTaskExecuteEngine的类图。它实现了nacosTaskEcecuteEngine,继承了NacosDelayTaskExecuteEngineNacosDelayTaskExecuteEngine又继承了AbstractNacosTaskExecuteEngine

5、Nacos 服务注册服务端源码分析(四)_第2张图片

先看下接口NacosTaskExecuteEngine。它定义了对NacosTaskNacosTaskProcessor的操作。

public interface NacosTaskExecuteEngine<T extends NacosTask> extends Closeable {
    // 属性判断
    int size();
    boolean isEmpty();
	// process的操作
    void addProcessor(Object key, NacosTaskProcessor taskProcessor);
    void removeProcessor(Object key);
    NacosTaskProcessor getProcessor(Object key);
    Collection<Object> getAllProcessorKey();
    void setDefaultTaskProcessor(NacosTaskProcessor defaultTaskProcessor);
    //  task的操作
    void addTask(Object key, T task);
    T removeTask(Object key);
    Collection<Object> getAllTaskKeys();
}

接下来我们看下抽样的模板AbstractNacosTaskExecuteEngine。它有两个私有变量,分别是ConcurrentHashMap类型的taskProcessors,这个是对处理类NacosTaskProcessor的缓存。另一个是NacosTaskProcessor,这是一个默认的处理类。缓存中不存在的话,就用这个类去处理。

public abstract class AbstractNacosTaskExecuteEngine<T extends NacosTask> implements NacosTaskExecuteEngine<T> {
    private final Logger log;
    // 保存了NacosTaskProcessor类的一个映射关系
    private final ConcurrentHashMap<Object, NacosTaskProcessor> taskProcessors = new ConcurrentHashMap<>();
    // 默认的NacosTaskProcessor,当无法找到对应的NacosTaskProcessor,使用这个类处理
    private NacosTaskProcessor defaultTaskProcessor;
    public AbstractNacosTaskExecuteEngine(Logger logger) {
        this.log = null != logger ? logger : LoggerFactory.getLogger(AbstractNacosTaskExecuteEngine.class.getName());
    }
    @Override
    public void addProcessor(Object key, NacosTaskProcessor taskProcessor) {
        // 没有才添加
        taskProcessors.putIfAbsent(key, taskProcessor);
    }
    @Override
    public void removeProcessor(Object key) {
        taskProcessors.remove(key);
    }
    
    @Override
    public NacosTaskProcessor getProcessor(Object key) {
        // 不在缓存中就取默认的
        return taskProcessors.containsKey(key) ? taskProcessors.get(key) : defaultTaskProcessor;
    }
    @Override
    public Collection<Object> getAllProcessorKey() {
        return taskProcessors.keySet();
    }
    @Override
    public void setDefaultTaskProcessor(NacosTaskProcessor defaultTaskProcessor) {
        this.defaultTaskProcessor = defaultTaskProcessor;
    }
    protected Logger getEngineLog() {
        return log;
    }
}

NacosDelayTaskExecuteEngine通过名字就可以知道这是执行可延迟的任务的。那它的延迟体现在哪里呢?

public class NacosDelayTaskExecuteEngine extends AbstractNacosTaskExecuteEngine<AbstractDelayTask> {
    // 私有变量,可以定时执行的服务
    private final ScheduledExecutorService processingExecutor;
    // 私有变量,AbstractDelayTask的缓存
    protected final ConcurrentHashMap<Object, AbstractDelayTask> tasks;
    // 构造方法
    public NacosDelayTaskExecuteEngine(String name, int initCapacity, Logger logger, long processInterval) {
        super(logger);
        tasks = new ConcurrentHashMap<>(initCapacity);
        // 创建了一个单线程的定时任务线程池
        processingExecutor = ExecutorFactory.newSingleScheduledExecutorService(new NameThreadFactory(name));
        // 开启线程池,默认每隔100ms执行ProcessRunnable
       processingExecutor
                .scheduleWithFixedDelay(new ProcessRunnable(), processInterval, processInterval, TimeUnit.MILLISECONDS);
    }
    // 任务处理类
    private class ProcessRunnable implements Runnable {
        @Override
        public void run() {
            try {
                // 处理任务
                processTasks();
            } catch (Throwable e) {
                getEngineLog().error(e.toString(), e);
            }
        }
    }
    protected void processTasks() {
        Collection<Object> keys = getAllTaskKeys();
        for (Object taskKey : keys) {
            AbstractDelayTask task = removeTask(taskKey);
            if (null == task) {
                continue;
            }
            // 一个个处理,通过taskKey找到处理类,必须有处理类去处理
            NacosTaskProcessor processor = getProcessor(taskKey);
            if (null == processor) {
                getEngineLog().error("processor not found for task, so discarded. " + task);
                continue;
            }
            try {
                // ReAdd task if process failed
                if (!processor.process(task)) {
                    // 失败重试
                    retryFailedTask(taskKey, task);
                }
            } catch (Throwable e) {
                getEngineLog().error("Nacos task execute error ", e);
                retryFailedTask(taskKey, task);
            }
        }
    }
    // 重试逻辑,加入缓存,后续继续执行
    private void retryFailedTask(Object key, AbstractDelayTask task) {
        task.setLastProcessTime(System.currentTimeMillis());
        addTask(key, task);
    }
}

最后查看下PushDelayTaskExecuteEngine。在类的构造方法中就设置了默认的任务处理类PushDelayTaskProcessor。说明如果没有对应的key的任务处理类设置的话,就用这个处理类处理所有任务。实际上它也确实没有添加其他的处理类了。

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

private static class PushDelayTaskProcessor implements NacosTaskProcessor {
    private final PushDelayTaskExecuteEngine executeEngine;
    public PushDelayTaskProcessor(PushDelayTaskExecuteEngine executeEngine) {
        this.executeEngine = executeEngine;
    }

    @Override
    public boolean process(NacosTask task) {
        PushDelayTask pushDelayTask = (PushDelayTask) task;
        Service service = pushDelayTask.getService();
        // 分配并处理任务
        NamingExecuteTaskDispatcher.getInstance()
            .dispatchAndExecuteTask(service, new PushExecuteTask(service, executeEngine, pushDelayTask));
        return true;
    }
}

在分析完上面这几个核心类和方法后,我们现在可以回过头来分析下面这句代码了

// com.alibaba.nacos.naming.push.v2.NamingSubscriberServiceV2Impl#onEvent
delayTaskEngine.addTask(service, new PushDelayTask(service, PushConfig.getInstance().getPushTaskDelay()));
public void addTask(Object key, AbstractDelayTask newTask) {
    // 加锁防并发处理,key就是对应的服务
    lock.lock();
    try {
        AbstractDelayTask existTask = tasks.get(key);
        if (null != existTask) {
            // 服务存在就合并处理,这个由AbstractDelayTask的子类处理
            newTask.merge(existTask);
        }
        // 放入Map等待轮训处理
        tasks.put(key, newTask);
    } finally {
        lock.unlock();
    }
}

代码解释完了,解释下整体的流程

  1. 通过delayTaskEngine添加到tasks(这是一个ConcurrentHashMap)中

  2. delayTaskEngine有个单线程的延迟任务线程池processingExecutor在不断的获取任务

  3. 因为delayTaskEngine是一个PushDelayTaskExecuteEngine,在构造方法中只添加了默认的处理类PushDelayTaskProcessor,所以都是由PushDelayTaskProcessor中的process(NacosTask task)执行。

  4. 这个process(NacosTask task)又调用了NamingExecuteTaskDispatcher.getInstance() .dispatchAndExecuteTask(service, new PushExecuteTask(service, executeEngine, pushDelayTask))

也就是说绕了一大圈,我们还是没看到处理逻辑,作者写那么一大串代码是为啥呢?

乍一看,这样是为了任务和处理类的解耦,并且异步化的去执行,但是这里还有个很重要的单词delay。也就是不仅仅是做了异步化的处理,还将异步中堆积的任务进行了合并。减少真正处理的逻辑。既然可以delay,当后续事件我们只需要看终态的话,合并处理可以提高性能,吞吐量,减少IO和网络操作。

NamingExecuteTaskDispatcher

接着上述第4点的NamingExecuteTaskDispatcher.getInstance() .dispatchAndExecuteTask(service, new PushExecuteTask(service, executeEngine, pushDelayTask))继续往下分析。

NamingExecuteTaskDispatcher.getInstance()一看到就要反应过来是个单例,全局唯一。所以可以直接获取类,而不需要new或者反射出来。

// 私有变量
private final NacosExecuteTaskExecuteEngine executeEngine; 
private NamingExecuteTaskDispatcher() {
    // 构造方法初始化了一个NacosExecuteTaskExecuteEngine
    executeEngine = new NacosExecuteTaskExecuteEngine(EnvUtil.FUNCTION_MODE_NAMING, Loggers.SRV_LOG);
}
public void dispatchAndExecuteTask(Object dispatchTag, AbstractExecuteTask task) {
    // 在executeEngine添加了任务
    executeEngine.addTask(dispatchTag, task);
}
@Override
public void addTask(Object tag, AbstractExecuteTask task) {
    // 获取NacosTaskProcessor
    NacosTaskProcessor processor = getProcessor(tag);
    if (null != processor) {
        // 不为空,就用对应的processor处理
        processor.process(task);
        return;
    }
    // 没有对应的process, 采用公共的TaskExecuteWorker执行
    TaskExecuteWorker worker = getWorker(tag);
    worker.process(task);
}

大家有没有意识到这个addTask和上面好像,甚至类名也很像,这个叫NacosExecuteTaskExecuteEngine,上面的叫NacosDelayTaskExecuteEngine。一个是ExecuteTask,一个是DelayTask

其实它们都是继承于AbstractNacosTaskExecuteEngine。那为什么一个是ExecuteTask,一个是DelayTask呢?区别是什么呢?

带着问题,我们看看他们的不同之处。

带着问题分析可以让自己的目标更加清晰,更容易找到问题的本质

NacosExecuteTaskExecuteEngine的私有变量和构造方法开始分析

// 任务执行组
private final TaskExecuteWorker[] executeWorkers;
public NacosExecuteTaskExecuteEngine(String name, Logger logger, int dispatchWorkerCount) {
    super(logger);
    // 根据预设的组大小创建
    executeWorkers = new TaskExecuteWorker[dispatchWorkerCount];
    for (int mod = 0; mod < dispatchWorkerCount; ++mod) {
        // 对处理成员初始化
        executeWorkers[mod] = new TaskExecuteWorker(name, mod, dispatchWorkerCount, getEngineLog());
    }
}

我们再看下TaskEcecuteWorker

public TaskExecuteWorker(final String name, final int mod, final int total, final Logger logger) {
    this.name = name + "_" + mod + "%" + total;
    // 阻塞队列
    this.queue = new ArrayBlockingQueue<>(QUEUE_CAPACITY);
    this.closed = new AtomicBoolean(false);
    this.log = null == logger ? LoggerFactory.getLogger(TaskExecuteWorker.class) : logger;
    // 创建一个内部执行线程
    realWorker = new InnerWorker(this.name);
    // 开启线程
    realWorker.start();
}

private class InnerWorker extends Thread {
    InnerWorker(String name) {
        setDaemon(false);
        setName(name);
    }
    @Override
    public void run() {
        while (!closed.get()) {
            try {
                // 从阻塞队列获取任务
                Runnable task = queue.take();
                long begin = System.currentTimeMillis();
                // 执行阻塞队列中的任务
                task.run();
                long duration = System.currentTimeMillis() - begin;
                if (duration > 1000L) {
                    log.warn("task {} takes {}ms", task, duration);
                }
            } catch (Throwable e) {
                log.error("[TASK-FAILED] " + e, e);
            }
        }
    }
}

NacosExecuteTaskExecuteEngine更像一个简化版的线程池,它没有最大线程数,没有拒接策略等。它就是一个多线程执行引擎。每个线程有自己的一个阻塞队列。当有数据的时候就执行,没数据就阻塞等待。

我们也捋一下NamingExecuteTaskDispatcher的执行流程

  1. NamingExecuteTaskDispatcher通过dispatchAndExecuteTask(Object dispatchTag, AbstractExecuteTask task)将其放入NacosExecuteTaskExecuteEngine
  2. NacosExecuteTaskExecuteEngine针对找到NacosTaskProcessor直接用其处理(但是这段应该是保留逻辑,留给后续扩展用的,因为这个类并没有添加任何的NacosTaskProcessor也没有添加defaultTaskProcessor
  3. 没有找到NacosTaskProcessor用自带的线程组去处理

再次回到NamingExecuteTaskDispatcher.getInstance() .dispatchAndExecuteTask(service, new PushExecuteTask(service, executeEngine, pushDelayTask)),这里创建了PushExecuteTask。根据上述流程,肯定会由其线程去执行run方法。

public class PushExecuteTask extends AbstractExecuteTask {
    @Override
    public void run() {
        try {
            // 生成推送数据
            PushDataWrapper wrapper = generatePushData();
            // 获取客户端管理类
            ClientManager clientManager = delayTaskEngine.getClientManager();
            for (String each : getTargetClientIds()) {
                // 获取每个客户端
                Client client = clientManager.getClient(each);
                if (null == client) {
                    // means this client has disconnect
                    continue;
                }
                Subscriber subscriber = clientManager.getClient(each).getSubscriber(service);
                // 进行推送
                delayTaskEngine.getPushExecutor().doPushWithCallback(each, subscriber, wrapper,
                        new ServicePushCallback(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));
        }
    }
}

总结

本篇重点分析了TaskExecuteEngine以及两个子类NacosDelayTaskExecuteEngineNacosExecuteTaskExecuteEngine。分析了他们的不同之处。但是这里只分析了两种执行引擎的不同,但归根结底,还是Task的不同才导致了执行方式的不同。本篇并没有具体去分析Task的不同。还有在推送的时候又是如何做的呢?我们下篇再一同分析。

你可能感兴趣的:(Nacos,源码分析,java,缓存,开发语言)