1、背景
一组任务并发执行,如任务A、B、C、D,其中有状态依赖关系,如D依赖C,即C任务执行完毕后,D任务才能开始执行。
2、实现思路
2.1、接口定义
2.1.1、业务执行接口定义
(1) 业务执行(业务处理相关接口)( IDataHolder)
(2) 缓存清理接口(IReleaseCache)
2.1.2、任务管理接口定义
(1) 任务执行管理接口(IExecute)
(2) 锁管理接口(任务依赖管理)(ILockMgr)
以上接口设计,接口各司其能,耦合性降低,简单明确,易于扩展。其中IDataHolder提供AbstractDataHolder抽象类实现,其中定义模板方法(定义为final),固定业务处理行为。
2.2、工厂生成实例
定义DataHolderFactory生成不同类型的IDataHolder实例
2.3、任务有向图设计
(1) 定义任务节点(Task),包含具体要执行的任务,父亲节点(标识依赖的任务),孩子节点(构建任务树),及锁成员(标识本任务是否执行完毕)
(2) 定义有向图(TaskGraph),包含头结点(Task,仅标识任务开始,无真正要执行的业务),“最宽”层的节点个数(作为线程池执行任务时的最大可并发数,可节省线程资源,减少依赖任务执行时的无效等待)。提供对外接口,返回要执行的任务列表(层次/广度遍历图),这样线程池在执行时会优先提交父任务,尽量减少子任务等待父任务的情况,提升效率
2.4、任务执行管理类
TaskMgr负责整体调度所有的任务,接收输入为需要处理的任务类型Map
2.5、对外服务类
DiffService负责对外提供数据处理服务,接收参数场景上下文,初始化任务类型依赖关系,调起TaskMgr中调度任务接口
2.6、设计特点
1、简洁、分工明确、结构清晰
2、耦合性低,相比于之前版本,任务之间的依赖关系上移,更加清晰明确
3、资源占用较少,线程池有序处理任务,限制最大线程数量,分批执行任务,同时通过合理设置任务提交顺序及线程池最大线程数量,提交吞吐量
4、扩展性强,每次扩展只需要修改DiffService中构建任务依赖部分,及扩展具体业务执行类即可,满足开闭原则。
2.7、缺陷
1、任务调度还是被Task管理,而不是由上层调度框架控制,这样吞吐量及性能无法达到最优
2、线程池执行任务时,某些情况依然可能出现大量的等待,造成资源浪费
最终演进,需参考Spark的任务调度框架,Stage切分,状态管理,任务调度等实现最优的资源分配。
3、代码实现
3.1、业务执行接口定义
public interface IDataHolder extends IReleaseCache, ITimeMgr
{
/**
* getOriginalDatas 获取WhatIf调整前后的源数据<br>
*
*/
public Map<EnumWhatIfStep, Object> getOriginalDatas();
/**
* 设置原始数据<br>
*
*/
public void setOriginalData(EnumWhatIfStep whatIfStep, Object object);
/**
* 生成差异数据及结果统计信息<br>
*/
public void diffAndStats();
/**
* DIFF数据、Summary信息持久化 persist<br>
*/
public void persist();
/**
* processDataDiff<br>
* 进行数据差异对比,并把结果入库<br>
*/
public void processDataDiff();
}
public interface IReleaseCache
{
/**
* releaseCacheInMemory<br>
* 释放内存中的缓存<br>
void releaseCacheInMemory();
/**
* clearDataInDb<br>
* 清除数据库中的数据<br>
*/
void clearDataInDb();
}
3.2、工厂构建业务任务实例及抽象类
public class DataHolderFactory
{
/**
* createDataHolder <br>
*
* @param type 类型
* @param dataHolders 依赖的dataHolder
* @return IDataHolder
*/
public static IDataHolder createDataHolder(EnumBusinessType type)
{
if (type == EnumBusinessType.xxx)
{
return new XXXDataHolder();
}
else if (type == EnumBusinessType.xxx)
{
return new XXXDataHolder();
}
else if (type == EnumBusinessType.xxx)
{
return new XXXDataHolder();
}
else if (type == EnumBusinessType. xxx)
{
return new XXXDataHolder ();
}
return null;
}
public abstract class AbstractDiffDataHolder<T> implements IDataHolder
{
private static final Logger LOGGER = LoggerFactory.getLog(AbstractDiffDataHolder.class);
/**
* 任务完成需要的最大毫秒数
*/
private static final long TASK_COMPLETE_MAX_MILLISECONDS = 300000L;
protected Object originalDataBeforeIf;
protected Object originalDataAfterIf;
/**
* Diff数据缓存
*/
@Getter
@Setter
private List<T> diffCache = new ArrayList<T>();
/*
* 统计信息缓存
*/
@Getter
@Setter
private BaseSummary summary;
/**
* startDataDiff<br>
* 开始数据对比,并结果入库<br>
*/
@Override
public final void processDataDiff()
{
// 1、清除上次生成的diff数据
this.clearDataInDb();
// 2、获取调整前和调整后的源数据
Map<EnumWhatIfStep, Object> originalDatas = this.getOriginalDatas();
// 3、设置源数据到originalDataBeforeIf和originalDataAfterIf中
Object originalDataBefore = originalDatas.get(EnumWhatIfStep.BEFORE);
Object originalDataAfter = originalDatas.get(EnumWhatIfStep.AFTER);
if (null == originalDataBefore || null == originalDataAfter)
{
LOGGER.error("Invalid data, original data(before or after) is empty, task finish.");
return;
}
this.setOriginalData(EnumWhatIfStep.BEFORE, originalDataBefore);
this.setOriginalData(EnumWhatIfStep.AFTER, originalDataAfter);
// 4、数据对比,产生对比差异数据diff data和统计数据Summary data
this.diffAndStats();
// 5、结果入库,差异数据、统计数据入库
this.persist();
// 6、释放缓存加快GC
releaseCacheInMemory();
}
@Override
public final void releaseCacheInMemory()
{
originalDataBeforeIf = null;
originalDataAfterIf = null;
diffCache = null;
summary = null;
}
@Override
public long requiredMilliSeconds()
{
return TASK_COMPLETE_MAX_MILLISECONDS;
}
@Override
public abstract void setOriginalData(EnumWhatIfStep whatIfStep, Object object);
@Override
public abstract Map<EnumWhatIfStep, Object> getOriginalDatas();
@Override
public abstract void diffAndStats();
@Override
public abstract void persist();
@Override
public abstract void clearDataInDb();
}
3.3、任务接口定义
/**
* ITimeMgr<br>
*/
public interface ITimeMgr
{
/**
* requiredMilliSeconds<br>
*
* @return 需要的毫秒数
*/
long requiredMilliSeconds();
}
public interface IExecute
{
/**
* preExecute<br>
* 执行前准备<br>
*/
void preExecute();
/**
* postExecute<br>
* 执行后处理<br>
*/
void postExecute();
}
public interface ILockMgr
{
/**
* initLock<br>
*/
void initLock();
/**
* getLock<br>
*/
CountDownLatch getLock();
/**
* releaseLock<br>
void releaseLock();
}
/**
* ICreateTask<br>
*
*/
public interface ICreateTask<T, R>
{
/**
* createTaskInstance<br>
* 根据任务类型创建任务实例<br>
*
* @param type 创建任务实例
* @return 任务实例
*/
public abstract T createTaskInstance(R type);
}
3.4、任务有向图构建
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import lombok.Getter;
import lombok.Setter;
public class Task<T extends ITimeMgr> implements IExecute, ILockMgr
{
private static final Logger LOGGER = LoggerFactory.getLog(Task.class);
private static final TimeUnit TIME_UNIT_MILLISECONDS = TimeUnit.MILLISECONDS;
/**
* 当前任务
*/
@Getter
@Setter
private T task;
/**
* 依赖的父任务列表
*/
@Getter
private List<Task<T>> parents = new LinkedList<>();
/**
* 依赖此任务的任务列表
*/
@Getter
private List<Task<T>> children = new LinkedList<>();
/**
* 当前任务的锁
*/
private CountDownLatch countDownLatch;
/**
* 构造函数<br>
*
* @param task 具体任务
*/
public Task(T task)
{
// 初始化任务
this.task = task;
}
@Override
public void initLock()
{
this.countDownLatch = new CountDownLatch(1);
}
@Override
public CountDownLatch getLock()
{
return this.countDownLatch;
}
@Override
public void releaseLock()
{
if (this.countDownLatch != null)
{
this.countDownLatch.countDown();
}
}
@Override
public void preExecute()
{
// 执行任务前需要等待依赖的父任务全部执行完毕
for (Task<T> parent : parents)
{
// 获取父任务的锁
CountDownLatch latch = parent.getLock();
if (latch == null)
{
continue;
}
// 存在锁,需要等待锁释放
try
{
// 等待父任务执行完毕, 超时时间由业务决定
latch.await(task.requiredMilliSeconds(), TIME_UNIT_MILLISECONDS);
}
catch (InterruptedException e)
{
LOGGER.error(e, "an interruption occurred.");
}
}
}
@Override
public void postExecute()
{
// 执行完毕后释放锁
this.releaseLock();
}
}
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import org.apache.commons.collections.CollectionUtils;
/**
* TaskGraph<br>
* 任务有向图<br>
* @param <T> 具体业务实例类
* @param <R> 具体业务对应类型
*/
public abstract class TaskGraph<T extends ITimeMgr, R> implements ICreateTask<T, R>
{
/**
* 根节点
*/
private Task<T> root;
/**
* "最宽"层的节点数量
*/
private int maxLevelTaskNumber;
/**
* 构造函数<br>
* 构建任务有向图<br>
*
* @param taskTypeDepends 任务类型依赖关系
*/
public TaskGraph(Map<R, List<R>> taskTypeDepends)
{
if (taskTypeDepends != null)
{
// 构建图
this.root = buildGraph(taskTypeDepends);
// 初始化为任务数量
this.maxLevelTaskNumber = taskTypeDepends.size();
}
}
/**
* getToDoTasks<br>
* 广度遍历图,按层输出任务列表,除去根节点<br>
*
* @return 除根节点外的任务列表,层次访问任务顺序加入返回的任务列表
*/
public final List<Task<T>> getToDoTasksAndUpdateMaxLevelTaskNum()
{
List<Task<T>> tasks = new ArrayList<Task<T>>();
// 1、根节点为空 或者 子任务为空
if (this.root == null || CollectionUtils.isEmpty(this.root.getChildren()))
{
return tasks;
}
// 2、层次遍历任务,添加到列表中
Queue<Task<T>> queue = new LinkedList<Task<T>>();
queue.add(this.root);
// 3、初始化最宽层的节点数量为1
int curMaxLevelTaskNumber = 1;
// 当前层节点数量
int currentLevelTaskNumber = 1;
// 下一层节点数量
int nextLevelTaskNumber = 0;
// 遍历队列
while (!queue.isEmpty())
{
// 3.1、弹出队列第一个元素
Task<T> curTask = queue.poll();
// 更新当前层节点数(减1)
currentLevelTaskNumber--;
// 3.2、任务加入任务列表,头结点没有要执行的任务体,所以不加入任务列表
if (curTask != this.root)
{
// 如果某任务跨层依赖,可能该任务先于依赖的任务添加到列表中,为保证该任务最后添加到任务列表中,需要删除之前添加的任务记录
delDupEleAndAddEleToList(curTask, tasks);
}
// 3.3、获取子任务列表,添加子任务到队列中
List<Task<T>> children = curTask.getChildren();
if (children.size() > 0)
{
// 添加子任务到队列中,任务在队列中,不重复添加
for (Task<T> childTask : children)
{
// 任务不在队列中,则加入队列,下层任务数量加1
if (!isTaskInContainer(childTask, queue))
{
queue.offer(childTask);
nextLevelTaskNumber += 1;
}
}
}
// 3.4、当前层遍历完毕,更新当前、下层及最宽层节点数量
if (currentLevelTaskNumber == 0)
{
// 更新最宽层节点数量
curMaxLevelTaskNumber = Math.max(curMaxLevelTaskNumber, nextLevelTaskNumber);
// 更新当前层及下一层节点数量
currentLevelTaskNumber = nextLevelTaskNumber;
nextLevelTaskNumber = 0;
}
}
// 更新Graph的最宽层节点数量
this.maxLevelTaskNumber = curMaxLevelTaskNumber;
return tasks;
}
// 删除列表中与要添加的目标元素重复的元素,并添加目标元素
private void delDupEleAndAddEleToList(Task<T> task, List<Task<T>> taskList)
{
// 1、使用迭代器删除与目标添加元素重复的元素
Iterator<Task<T>> iter = taskList.iterator();
while (iter.hasNext())
{
if (iter.next() == task)
{
iter.remove();
}
}
// 添加目标元素
taskList.add(task);
}
// 任务是否已经存在于任务列表中
private boolean isTaskInContainer(Task<T> task, Queue<Task<T>> taskContainer)
{
for (Task<T> curTask : taskContainer)
{
if (task.getTask() == curTask.getTask())
{
return true;
}
}
return false;
}
/**
* getMaxLevelTaskNumber<br>
* 输出"最宽"层的节点数量<br>
*
* @return "最宽"层的节点数量
*/
public final int getMaxLevelTaskNumber()
{
return this.maxLevelTaskNumber;
}
/**
* buildGraph<br>
* 根据任务类型依赖构建任务依赖图<br>
*
* @param taskTypeDepends 任务类型依赖Map
* @return Task<T> 图的根节点
*/
private Task<T> buildGraph(Map<R, List<R>> taskTypeDepends)
{
// 1、初始化需要的变量与容器
// 1.1、节点数量
int taskSize = taskTypeDepends.size();
// 1.2、初始化任务实例Map
Map<R, T> instances = new HashMap<>(taskSize);
// 1.3、初始化根任务节点(没有挂接子任务)
Task<T> taskRoot = buildBaseRootTask();
// 1.4、初始化要执行的任务节点Map
Map<R, Task<T>> tasks = new HashMap<>(taskSize);
// 2、遍历任务类型依赖集合,构建依赖任务对象
for (Entry<R, List<R>> entry : taskTypeDepends.entrySet())
{
// 2.1、获取当前任务类型
R type = entry.getKey();
// 2.2、取得当前任务实例,不存在则创建并放入容器中
T taskInstance = getOrCreateInstance(type, instances);
// 2.3、任务没有依赖则挂接到根节点下,有任务依赖则获取当前指向的任务,并完成依赖的任务的挂接
// 获取当前任务类型对应的任务
Task<T> curTask = getOrCreateTask(type, taskInstance, tasks);
// 获取依赖的任务类型
List<R> dependTypes = entry.getValue();
// 2.3.1、当前任务无依赖,则把当前任务挂接到根节点即可
if (dependTypes == null || dependTypes.isEmpty())
{
// 根节点下挂接第一批要执行的任务
addChildTaskToParent(taskRoot, curTask);
}
// 2.3.2、当前任务有依赖,则挂接依赖的任务到当前任务下
else
{
for (R dependType : dependTypes)
{
// 获取依赖的任务类型对应的T实例
T dependTaskInstance = getOrCreateInstance(dependType, instances);
// 获取依赖的任务
Task<T> dependTask = getOrCreateTask(dependType, dependTaskInstance, tasks);
// 挂接依赖的任务与当前任务
addChildTaskToParent(dependTask, curTask);
}
}
}
// 3、返回根任务节点
return taskRoot;
}
/**
* createTaskInstance<br>
* 根据任务类型创建任务实例<br>
*
* @param type 创建任务实例
* @return 任务实例
*/
@Override
public abstract T createTaskInstance(R type);
// 从实例集合中取出T实例,不存在则新建实例放入Map中
private T getOrCreateInstance(R type, Map<R, T> instances)
{
T taskInstance = instances.get(type);
if (taskInstance == null)
{
// 没有则创建对应任务实例
taskInstance = createTaskInstance(type);
// 缓存到Map中
instances.put(type, taskInstance);
}
return taskInstance;
}
// 从任务集合中取出任务实例,不存在则新建任务放入Map中
private Task<T> getOrCreateTask(R type, T taskInstance, Map<R, Task<T>> tasks)
{
Task<T> task = tasks.get(type);
if (task == null)
{
// 没有则创建对应任务
task = new Task<T>(taskInstance);
// 初始化锁
task.initLock();
// 缓存到Map中
tasks.put(type, task);
}
return task;
}
// 构建图的基础根任务节点(没有挂接子任务),标识任务的开始,根任务没有真正需要执行的任务实体
private Task<T> buildBaseRootTask()
{
// 1、构建没有任务执行的空任务节点(没有挂接子任务)
Task<T> emptyTask = new Task<T>(null);
// 2、返回空任务节点
return emptyTask;
}
// 添加子任务到父任务列表中, 子任务parents成员加入父任务
private void addChildTaskToParent(Task<T> parent, Task<T> child)
{
// 父任务children挂接子任务
parent.getChildren().add(child);
// 子任务parent指向父任务
child.getParents().add(parent);
}
}
import java.util.List;
import java.util.Map;
/**
* TaskGraph<br>
* 任务有向图<br>
*/
public class DiffTaskGraph extends TaskGraph<IDataHolder, EnumBusinessType>
{
public DiffTaskGraph(Map<EnumBusinessType, List<EnumBusinessType>> taskTypeDepends)
{
super(taskTypeDepends);
}
@Override
public IDataHolder createTaskInstance(EnumBusinessType type)
{
return DiffDataHolderFactory.createDiffDataHolder(type);
}
}
3.5、任务管理执行类
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* DiffTaskMgr<br>
* 数据对比任务管理类<br>
*
*/
public class DiffTaskMgr
{
private static final Logger LOGGER = LoggerFactory.getLog(DiffTaskMgr.class);
/**
* executeTasks<br>
* 多线程执行diff任务<br>
*
* @param taskTypeDepends 任务类型依赖集合
* @param projectId 工程Id
* @param scenarioId 场景Id
*/
public static void executeTasks(Map<EnumBusinessType, List<EnumBusinessType>> taskTypeDepends, String projectId, String scenarioId)
{
LOGGER.info("Diff tasks start");
if (taskTypeDepends == null)
{
LOGGER.error("Invalid task param.");
return;
}
// 1、构建要执行的任务有向图
DiffTaskGraph taskGraph = new DiffTaskGraph(taskTypeDepends);
// 2、获取要执行的任务列表
List<Task<IDataHolder>> toDoTasks = taskGraph.getToDoTasksAndUpdateMaxLevelTaskNum();
int taskSize = toDoTasks.size();
if (taskSize <= 0)
{
LOGGER.error("To do tasks is empty.");
return;
}
// 3、任务最大可并行数量
int maxParallelNum = taskGraph.getMaxLevelTaskNumber();
// 4、初始化线程池 自定义线程池
ExecutorService executor = new ThreadPoolExecutor(maxParallelNum, maxParallelNum, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(taskSize));
// 5、创建多线程任务执行器
CompletionService<Void> completionService = new ExecutorCompletionService<Void>(
executor);
// 6、提交Diff任务
for (Task<IDataHolder> curTask : toDoTasks)
{
completionService.submit(new Callable<Void>()
{
@Override
public Void call() throws Exception
{
// 设置上下文
ThreadContextContainer.setCurrPrjSceId(projectId, scenarioId);
// 执行diff任务
runDiffTask(curTask);
// 返回Void实例,这里返回null即可
return null;
}
});
}
// 7、等待所有任务完成
try
{
for (int taskCount = 0; taskCount < taskSize; taskCount++)
{
completionService.take().get();
}
}
catch (InterruptedException | ExecutionException e)
{
LOGGER.error(e, "Diff Task InterruptedException | ExecutionException.");
}
catch (Exception e)
{
LOGGER.error(e, "Diff Task exception.");
}
finally
{
// 8、关闭线程池
try
{
executor.shutdownNow();
executor.awaitTermination(DiffConst.TIMEOUT, DiffConst.TIME_UNIT_SECONDS);
}
catch (InterruptedException e)
{
LOGGER.error(e, "Executor service shut down fail, Exception is: ");
}
}
LOGGER.info("All diff tasks end.");
}
// 执行单个diff任务
private static void runDiffTask(Task<IDataHolder> curTask)
{
// 1、等待parent任务执行完毕
curTask.preExecute();
// 2、执行diff任务
try
{
curTask.getTask().processDataDiff();
}
finally
{
// 3、设置本任务状态为完成
curTask.postExecute();
}
}
}
3.6、对外服务类
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.google.common.collect.ImmutableList;
/**
* DiffService WhatIf调整前后 结果对比服务类<br>
*
*/
@Service
public class DiffService
{
private static final Logger LOGGER = LoggerFactory.getLog(DiffService.class);
/**
* 要执行的任务类型及依赖关系
*/
private static final Map<EnumBusinessType,List<EnumBusinessType>> BUSINESS_TYPES_DEPENDS = new HashMap<>();
static
{
BUSINESS_TYPES_DEPENDS.put(EnumBusinessType.XXX, null);
...
BUSINESS_TYPES_DEPENDS.put(EnumBusinessType.LOAD, ImmutableList.of(EnumBusinessType.XXX));
...
}
private SummaryDataMgr summaryDataMgr;
/**
* processDataDiff 生成flow、tunnel、load等差异数据并入库<br>
*
* @param projectId projectId
* @param scenarioId scenarioId
*/
public void processDataDiff(String projectId, String scenarioId)
{
// 1、准备工作
doPre(projectId, scenarioId);
// 2、触发diff & summary 计算、入库
DiffTaskMgr.executeTasks(getTaskTypeDepends(), projectId, scenarioId);
// 3、后续处理工作
doPost(projectId, scenarioId);
}
private Map<EnumBusinessType, List<EnumBusinessType>> getTaskTypeDepends()
{
return BUSINESS_TYPES_DEPENDS;
}
private void doPre(String projectId, String scenarioId)
{
// 1、设置工程场景上下文
// 2、清除summary数据
}
private void doPost(String projectId, String scenarioId)
{
// 清理缓存等
}
}
参考:
任务调度系统-任务依赖的设计
数据结构-图
原型篇–有依赖的并发任务执行-代码实践(工厂、模板模式、线程池的使用)
一个著名的任务调度系统是如何设计的
spark job stage task介绍