上篇分析事件的订阅者,讲解了几个重要事件的关系。本篇从ServiceEvent.ServiceChangedEvent
全面分析后续的处理逻辑。
通过调用分析,我们可以看到NamingSubscriberServiceV2Impl
处理的这个事件,其他都是发布事件。
看下这个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()));
}
}
这里有两个很重要的概念PushDelayTaskExecuteEngine
和PushDelayTask
。我们先分析下这两个类。等分析完这两个类后,再看后续逻辑就很简单了。对于优秀的设计,我们花多点时间去消化和理解是很重要的。
我们先看下PushDelayTaskExecuteEngine
的类图。它实现了nacosTaskEcecuteEngine
,继承了NacosDelayTaskExecuteEngine
,NacosDelayTaskExecuteEngine
又继承了AbstractNacosTaskExecuteEngine
先看下接口NacosTaskExecuteEngine
。它定义了对NacosTask
和NacosTaskProcessor
的操作。
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();
}
}
代码解释完了,解释下整体的流程
通过delayTaskEngine
添加到tasks
(这是一个ConcurrentHashMap
)中
delayTaskEngine
有个单线程的延迟任务线程池processingExecutor
在不断的获取任务
因为delayTaskEngine
是一个PushDelayTaskExecuteEngine
,在构造方法中只添加了默认的处理类PushDelayTaskProcessor
,所以都是由PushDelayTaskProcessor
中的process(NacosTask task)
执行。
这个process(NacosTask task)
又调用了NamingExecuteTaskDispatcher.getInstance() .dispatchAndExecuteTask(service, new PushExecuteTask(service, executeEngine, pushDelayTask))
。
也就是说绕了一大圈,我们还是没看到处理逻辑,作者写那么一大串代码是为啥呢?
乍一看,这样是为了任务和处理类的解耦,并且异步化的去执行,但是这里还有个很重要的单词delay
。也就是不仅仅是做了异步化的处理,还将异步中堆积的任务进行了合并。减少真正处理的逻辑。既然可以delay
,当后续事件我们只需要看终态的话,合并处理可以提高性能,吞吐量,减少IO和网络操作。
接着上述第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
的执行流程
NamingExecuteTaskDispatcher
通过dispatchAndExecuteTask(Object dispatchTag, AbstractExecuteTask task)
将其放入NacosExecuteTaskExecuteEngine
NacosExecuteTaskExecuteEngine
针对找到NacosTaskProcessor
直接用其处理(但是这段应该是保留逻辑,留给后续扩展用的,因为这个类并没有添加任何的NacosTaskProcessor
也没有添加defaultTaskProcessor
)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
以及两个子类NacosDelayTaskExecuteEngine
和NacosExecuteTaskExecuteEngine
。分析了他们的不同之处。但是这里只分析了两种执行引擎的不同,但归根结底,还是Task
的不同才导致了执行方式的不同。本篇并没有具体去分析Task
的不同。还有在推送的时候又是如何做的呢?我们下篇再一同分析。