PowerJob版本:4.3.2-main。
之前分析过PowerJob的时间轮源码,感兴趣的可以查看《较真儿学源码系列-PowerJob时间轮源码分析》
1 简介
MapReduce是一种编程模型,以及在集群上使用并行、分布式算法处理和生成大数据集的相关实现。
一个MapReduce程序由一个map过程和reduce方法组成,map过程执行过滤和排序(例如按名字将学生分成不同的队列,每个名字一个队列),reduce方法执行聚合操作(例如计算每个队列中的学生人数,得出姓名的频率)。“MapReduce系统”(也称为“基础设施”或“框架”)通过调度分布式服务器、并行运行各种任务、管理系统中各部分之间的所有通信和数据传输以及提供冗余和容错机制来协调处理过程。
该模型是数据分拆-应用-合并策略的特化版,用于数据分析;其灵感来自函数式编程中常用的map和reduce函数,但在MapReduce框架中,它们的目的与其原始形式并不相同。MapReduce框架的关键贡献并不在于实际的map和reduce函数(这些函数类似于1995年消息传递接口标准中的reduce和scatter操作),而在于通过并行化实现的各种应用程序的可扩展性和容错性。因此,单线程的MapReduce实现通常不会比传统的(非MapReduce)实现更快;任何性能提升通常只能在多线程的多处理器硬件上实现。只有当优化分布式shuffle操作(这降低了网络通信成本)和MapReduce框架的容错功能发挥作用时,使用这种模型才有益。优化通信成本是设计一个好的MapReduce算法的必要条件。
MapReduce库已经在许多编程语言中实现,并具有不同程度的优化。Apache Hadoop是一个流行的开源实现,支持分布式shuffle操作。MapReduce最初指的是Google的专有技术,但后来被通用化。到2014年,Google不再使用MapReduce作为其大数据处理的主要模型,而Apache Mahout的开发则转向更强大且更少依赖磁盘的机制,这些机制包含了完整的map和reduce功能。
以上是MapReduce的解释,而MapReduce框架(或系统)通常由三个操作(或步骤)组成:
- Map:每个工作节点将map函数应用于本地数据,并将输出写入临时存储器。master节点确保仅处理一份冗余输入数据的副本。
- Shuffle:工作节点基于输出键(由map函数产生)重新分配数据,使得属于一个键的所有数据都位于同一个工作节点上。
- Reduce:现在,工作节点并行处理每个键的输出数据组。
而在PowerJob中也提供了类似MapReduce的功能,在面对大数据量的离线任务时会很合适。
2 使用
我们使用PowerJob的MapReduce功能,需要实现MapReduceProcessor接口。
可以看到,MapReduceProcessor接口继承了MapProcessor接口,而MapProcessor接口又继承了BasicProcessor接口。MapProcessor中含有默认的map方法,调用该方法即可完成分发子任务的过程。而实现MapReduceProcessor接口的类中需要同时实现reduce方法,完成最后的汇总过程。具体的使用如下所示:
/**
* MapReduce 处理器示例
* 控制台参数:{"batchSize": 100, "batchNum": 2}
*
* @author tjq
* @since 2020/4/17
*/
@Slf4j
@Component
public class MapReduceProcessorDemo implements MapReduceProcessor {
@Override
public ProcessResult process(TaskContext context) throws Exception {
OmsLogger omsLogger = context.getOmsLogger();
log.info("============== TestMapReduceProcessor#process ==============");
log.info("isRootTask:{}", isRootTask());
log.info("taskContext:{}", JsonUtils.toJSONString(context));
// 根据控制台参数获取MR批次及子任务大小
final JSONObject jobParams = JSONObject.parseObject(context.getJobParams());
Integer batchSize = (Integer) jobParams.getOrDefault("batchSize", 100);
Integer batchNum = (Integer) jobParams.getOrDefault("batchNum", 10);
if (isRootTask()) {
log.info("==== MAP ====");
omsLogger.info("[DemoMRProcessor] start root task~");
List<TestSubTask> subTasks = Lists.newLinkedList();
for (int j = 0; j < batchNum; j++) {
for (int i = 0; i < batchSize; i++) {
int x = j * batchSize + i;
subTasks.add(new TestSubTask("name" + x, x));
}
map(subTasks, "MAP_TEST_TASK");
subTasks.clear();
}
omsLogger.info("[DemoMRProcessor] map success~");
return new ProcessResult(true, "MAP_SUCCESS");
} else {
log.info("==== NORMAL_PROCESS ====");
omsLogger.info("[DemoMRProcessor] process subTask: {}.", JSON.toJSONString(context.getSubTask()));
log.info("subTask: {}", JsonUtils.toJSONString(context.getSubTask()));
Thread.sleep(1000);
if (context.getCurrentRetryTimes() == 0) {
return new ProcessResult(false, "FIRST_FAILED");
} else {
return new ProcessResult(true, "PROCESS_SUCCESS");
}
}
}
@Override
public ProcessResult reduce(TaskContext context, List<TaskResult> taskResults) {
log.info("================ MapReduceProcessorDemo#reduce ================");
log.info("TaskContext: {}", JSONObject.toJSONString(context));
log.info("List<TaskResult>: {}", JSONObject.toJSONString(taskResults));
context.getOmsLogger().info("MapReduce job finished, result is {}.", taskResults);
boolean success = true;
return new ProcessResult(success, context + ": " + success);
}
@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public static class TestSubTask {
private String name;
private int age;
}
}
以上是官方的例子,从中可以看到,根任务和子任务都会执行process方法,其中会使用到isRootTask方法来区分根任务和子任务。根任务会调用到map方法来分发子任务,而子任务才会执行实际的业务逻辑。reduce方法则会汇总所有子任务的执行结果。在控制台创建的任务如下:
而如果只想使用map过程,而不关心reduce、即子任务汇总上来的执行结果的话,则只需要实现MapProcessor接口就行了。
3 map方法
根任务会调用到map方法来分发子任务,查看其实现:
/**
* MapProcessor:
* 分发子任务
*
* @param taskList 子任务,再次执行时可通过 TaskContext#getSubTask 获取
* @param taskName 子任务名称,即子任务处理器中 TaskContext#getTaskName 获取到的值
* @throws PowerJobCheckedException map 失败将抛出异常
*/
default void map(List<?> taskList, String taskName) throws PowerJobCheckedException {
if (CollectionUtils.isEmpty(taskList)) {
return;
}
//从缓存中获取任务
TaskDO task = ThreadLocalStore.getTask();
WorkerRuntime workerRuntime = ThreadLocalStore.getRuntimeMeta();
if (taskList.size() > RECOMMEND_BATCH_SIZE) {
log.warn("[Map-{}] map task size is too large, network maybe overload... please try to split the tasks.", task.getInstanceId());
}
// 修复 map 任务命名和根任务名或者最终任务名称一致导致的问题(无限生成子任务或者直接失败)
//如果任务名是特殊名称的话(OMS_ROOT_TASK/OMS_LAST_TASK),需要改写,加上“X-”前缀
if (TaskConstant.ROOT_TASK_NAME.equals(taskName) || TaskConstant.LAST_TASK_NAME.equals(taskName)) {
log.warn("[Map-{}] illegal map task name : {}! please do not use 'OMS_ROOT_TASK' or 'OMS_LAST_TASK' as map task name. as a precaution, it will be renamed 'X-{}' automatically.", task.getInstanceId(), taskName, taskName);
taskName = "X-" + taskName;
}
// 1. 构造请求
ProcessorMapTaskRequest req = new ProcessorMapTaskRequest(task, taskList, taskName);
// 2. 可靠发送请求(任务不允许丢失,需要使用 ask 方法,失败抛异常)
boolean requestSucceed = TransportUtils.reliableMapTask(req, task.getAddress(), workerRuntime);
if (requestSucceed) {
log.info("[Map-{}] map task[name={},num={}] successfully!", task.getInstanceId(), taskName, taskList.size());
} else {
throw new PowerJobCheckedException("map failed for task: " + taskName);
}
}
/**
* ProcessorMapTaskRequest:
* 第31行代码处:
*/
public ProcessorMapTaskRequest(TaskDO parentTask, List<?> subTaskList, String taskName) {
this.instanceId = parentTask.getInstanceId();
this.subInstanceId = parentTask.getSubInstanceId();
this.taskName = taskName;
this.subTasks = Lists.newLinkedList();
subTaskList.forEach(subTask -> {
// 同一个 Task 内部可能多次 Map,因此还是要确保线程级别的唯一
String subTaskId = parentTask.getTaskId() + "." + ThreadLocalStore.getTaskIDAddr().getAndIncrement();
// 写入类名,方便反序列化
subTasks.add(new SubTask(subTaskId, SerializerUtils.serialize(subTask)));
});
}
/**
* TransportUtils:
* 第34行代码处:
*/
public static boolean reliableMapTask(ProcessorMapTaskRequest req, String address, WorkerRuntime workerRuntime) throws PowerJobCheckedException {
try {
return reliableAsk(ServerType.WORKER, WTT_PATH, WTT_HANDLER_MAP_TASK, address, req, workerRuntime.getTransporter()).isSuccess();
} catch (Throwable throwable) {
throw new PowerJobCheckedException(throwable);
}
}
/**
* TaskTrackerActor:
* 第68行代码处:
* 子任务 map 处理器
*/
@Handler(path = WTT_HANDLER_MAP_TASK)
public AskResponse onReceiveProcessorMapTaskRequest(ProcessorMapTaskRequest req) {
//获取taskTracker
HeavyTaskTracker taskTracker = HeavyTaskTrackerManager.getTaskTracker(req.getInstanceId());
if (taskTracker == null) {
log.warn("[TaskTrackerActor] receive ProcessorMapTaskRequest({}) but system can't find TaskTracker.", req);
return null;
}
boolean success = false;
List<TaskDO> subTaskList = Lists.newLinkedList();
try {
req.getSubTasks().forEach(originSubTask -> {
TaskDO subTask = new TaskDO();
//这里需要特别留意下,实际上是用调用map方法时传进来的任务名来生成子任务的
subTask.setTaskName(req.getTaskName());
subTask.setSubInstanceId(req.getSubInstanceId());
subTask.setTaskId(originSubTask.getTaskId());
subTask.setTaskContent(originSubTask.getTaskContent());
subTaskList.add(subTask);
});
success = taskTracker.submitTask(subTaskList);
} catch (Exception e) {
log.warn("[TaskTrackerActor] process map task(instanceId={}) failed.", req.getInstanceId(), e);
}
AskResponse response = new AskResponse();
response.setSuccess(success);
return response;
}
/**
* HeavyTaskTracker:
* 第107行代码处:
* 提交Task任务(MapReduce的Map,Broadcast的广播),上层保证 batchSize,同时插入过多数据可能导致失败
*
* @param newTaskList 新增的子任务列表
*/
public boolean submitTask(List<TaskDO> newTaskList) {
if (finished.get()) {
return true;
}
if (CollectionUtils.isEmpty(newTaskList)) {
return true;
}
// 基础处理(多循环一次虽然有些浪费,但分布式执行中,这点耗时绝不是主要占比,忽略不计!)
newTaskList.forEach(task -> {
task.setInstanceId(instanceId);
//将状态设置为“等待调度器调度”
task.setStatus(TaskStatus.WAITING_DISPATCH.getValue());
task.setFailedCnt(0);
task.setLastModifiedTime(System.currentTimeMillis());
task.setCreatedTime(System.currentTimeMillis());
task.setLastReportTime(-1L);
});
log.debug("[TaskTracker-{}] receive new tasks: {}", instanceId, newTaskList);
//这里就是在往task_info表中插入数据
return taskPersistenceService.batchSave(newTaskList);
}
可以看到,map方法的作用就是根据根任务创建了一堆子任务,这些子任务的名称都是调用map方法时传进来的任务名。最后,子任务全都持久化到task_info表中。那么这些数据是什么时候会被执行的呢?我们先带着这个疑惑。
之前我们在《较真儿学源码系列-PowerJob启动流程源码分析》的第2.3.1小节中分析过,服务端启动的时候,会执行一些初始化任务。其中在PowerScheduleService.scheduleNormalJob方法中会调用到HeavyTaskTracker.create方法。其中又会开启三个定时任务(StatusCheckRunnable、WorkerDetector和Dispatcher)。之前我们只分析了常规任务的StatusCheckRunnable和Dispatcher方法的执行逻辑,接下来就来分析下在MapReduce任务的实现下有什么不一样的地方:
3.1 StatusCheckRunnable
/**
* StatusCheckRunnable:
*/
@SuppressWarnings("squid:S3776")
private void innerRun() {
//获取任务实例产生的各个Task状态,用于分析任务实例执行情况
InstanceStatisticsHolder holder = getInstanceStatisticsHolder(instanceId);
long finishedNum = holder.succeedNum + holder.failedNum;
long unfinishedNum = holder.waitingDispatchNum + holder.workerUnreceivedNum + holder.receivedNum + holder.runningNum;
log.debug("[TaskTracker-{}] status check result: {}", instanceId, holder);
//组装上报参数
//...
boolean success = false;
String result = null;
// 2. 如果未完成任务数为0,判断是否真正结束,并获取真正结束任务的执行结果
if (unfinishedNum == 0) {
// 数据库中一个任务都没有,说明根任务创建失败,该任务实例失败
if (finishedNum == 0) {
finished.set(true);
result = SystemInstanceResult.TASK_INIT_FAILED;
} else {
ExecuteType executeType = ExecuteType.valueOf(instanceInfo.getExecuteType());
switch (executeType) {
case STANDALONE:
//...
break;
// MAP 不关心结果,最简单
case MAP:
finished.set(true);
success = holder.failedNum == 0;
result = String.format("total:%d,succeed:%d,failed:%d", holder.getTotalTaskNum(), holder.succeedNum, holder.failedNum);
break;
// MapReduce 和 Broadcast 任务实例是否完成根据**LastTask**的执行情况判断
default:
//MapReduce任务下查找是否含有最终任务(代码执行到这里,unfinishedNum == 0 并且finishedNum != 0,说明所有的根任务和子任务都执行完了)
Optional<TaskDO> lastTaskOptional = taskPersistenceService.getLastTask(instanceId, instanceId);
if (lastTaskOptional.isPresent()) {
// 存在则根据 reduce 任务来判断状态
TaskDO resultTask = lastTaskOptional.get();
TaskStatus lastTaskStatus = TaskStatus.of(resultTask.getStatus());
if (lastTaskStatus == TaskStatus.WORKER_PROCESS_SUCCESS || lastTaskStatus == TaskStatus.WORKER_PROCESS_FAILED) {
finished.set(true);
success = lastTaskStatus == TaskStatus.WORKER_PROCESS_SUCCESS;
result = resultTask.getResult();
}
} else {
// 不存在,代表前置任务刚刚执行完毕,需要创建 lastTask,最终任务必须在本机执行!
//最后会创建一个最终任务,该任务是来执行reduce方法的
TaskDO newLastTask = new TaskDO();
//这里需要留意下,最终任务的名称为OMS_LAST_TASK(LAST_TASK_NAME)
newLastTask.setTaskName(TaskConstant.LAST_TASK_NAME);
newLastTask.setTaskId(LAST_TASK_ID);
newLastTask.setSubInstanceId(instanceId);
newLastTask.setAddress(workerRuntime.getWorkerAddress());
//task_info表中插入数据
submitTask(Lists.newArrayList(newLastTask));
}
}
}
}
//...
}
之前我们分析过StatusCheckRunnable的innerRun方法,但是没有分析过MapReduce的处理。由上可以看到,因为Map任务不关心处理结果,所以没有什么具体的逻辑,不会上报结果;而MapReduce则不一样,在所有根任务和子任务都执行完了后,会查找有没有最终任务。有则判断其执行状态,没有则会新建(最终任务是用来执行reduce任务的)。
3.2 WorkerDetector
WorkerDetector是Map/MapReduce任务中检查是否需要注册新的worker节点用的:
/**
* WorkerDetector:
*/
@Override
public void run() {
//检查是否需要动态加载新的执行器
boolean needMoreWorker = ptStatusHolder.checkNeedMoreWorker();
log.info("[TaskTracker-{}] checkNeedMoreWorker: {}", instanceId, needMoreWorker);
//不需要就直接退出,不做任何处理
if (!needMoreWorker) {
return;
}
//获取当前服务端地址
final String currentServerAddress = workerRuntime.getServerDiscoveryService().getCurrentServerAddress();
if (StringUtils.isEmpty(currentServerAddress)) {
log.warn("[TaskTracker-{}] no server available, won't start worker detective!", instanceId);
return;
}
try {
WorkerQueryExecutorClusterReq req = new WorkerQueryExecutorClusterReq(workerRuntime.getAppId(), instanceInfo.getJobId());
AskResponse response = TransportUtils.reliableQueryJobCluster(req, currentServerAddress, workerRuntime.getTransporter());
if (!response.isSuccess()) {
log.warn("[TaskTracker-{}] detective failed due to ask failed, message is {}", instanceId, response.getMessage());
return;
}
List<String> workerList = JsonUtils.parseObject(response.getData(), new TypeReference<List<String>>() {
});
ptStatusHolder.register(workerList);
} catch (Exception e) {
log.warn("[TaskTracker-{}] detective failed, currentServer: {}", instanceId, currentServerAddress, e);
}
}
/**
* ProcessorTrackerStatusHolder:
* 第8行代码处:
* 检查是否需要动态加载新的执行器
*
* @return check need more workers
*/
public boolean checkNeedMoreWorker() {
if (endlessWorkerNum()) {
return true;
}
//可用ProcessorTracker的数量是否小于最大worker数量
return getAvailableProcessorTrackers().size() < maxWorkerCount;
}
/**
* 第46行代码处:
*/
private boolean endlessWorkerNum() {
//最大worker数量是否为空
return maxWorkerCount == null || maxWorkerCount == 0;
}
/**
* TransportUtils:
* 第24行代码处:
*/
@SneakyThrows
public static AskResponse reliableQueryJobCluster(WorkerQueryExecutorClusterReq req, String address, Transporter transporter) {
return reliableAsk(ServerType.SERVER, S4W_PATH, S4W_HANDLER_QUERY_JOB_CLUSTER, address, req, transporter);
}
/**
* AbWorkerRequestHandler:
* 第67行代码处:
*/
@Override
@Handler(path = S4W_HANDLER_QUERY_JOB_CLUSTER, processType = ProcessType.BLOCKING)
public AskResponse processWorkerQueryExecutorCluster(WorkerQueryExecutorClusterReq req) {
AskResponse askResponse;
Long jobId = req.getJobId();
Long appId = req.getAppId();
JobInfoRepository jobInfoRepository = SpringUtils.getBean(JobInfoRepository.class);
//从任务表中获取任务
Optional<JobInfoDO> jobInfoOpt = jobInfoRepository.findById(jobId);
if (jobInfoOpt.isPresent()) {
JobInfoDO jobInfo = jobInfoOpt.get();
if (!jobInfo.getAppId().equals(appId)) {
askResponse = AskResponse.failed("Permission Denied!");
} else {
//获取合适的worker列表并返回
List<String> sortedAvailableWorker = workerClusterQueryService.getSuitableWorkers(jobInfo)
.stream().map(WorkerInfo::getAddress).collect(Collectors.toList());
askResponse = AskResponse.succeed(sortedAvailableWorker);
}
} else {
askResponse = AskResponse.failed("can't find jobInfo by jobId: " + jobId);
}
return askResponse;
}
/**
* ProcessorTrackerStatusHolder:
* 第32行代码处:
*/
public void register(List<String> workerIpList) {
if (endlessWorkerNum()) {
//如果当前没有worker,则根据worker列表一一注册
workerIpList.forEach(this::registerOne);
return;
}
//获取可以派发任务的 ProcessorTracker
List<String> availableProcessorTrackers = getAvailableProcessorTrackers();
int currentWorkerSize = availableProcessorTrackers.size();
int needMoreNum = maxWorkerCount - currentWorkerSize;
if (needMoreNum <= 0) {
return;
}
log.info("[PTStatusHolder-{}] currentWorkerSize: {}, needMoreNum: {}", instanceId, currentWorkerSize, needMoreNum);
//在不超过最大worker数量的前提下,新注册worker(添加缓存)
for (String newIp : workerIpList) {
boolean success = registerOne(newIp);
if (success) {
needMoreNum--;
}
if (needMoreNum <= 0) {
return;
}
}
}
/**
* 第108行代码处和第123行代码处:
* 注册新的执行节点
* 添加缓存
*
* @param address 新的执行节点地址
* @return true: register successfully / false: already exists
*/
private boolean registerOne(String address) {
ProcessorTrackerStatus pts = address2Status.get(address);
if (pts != null) {
return false;
}
pts = new ProcessorTrackerStatus();
pts.init(address);
address2Status.put(address, pts);
log.info("[PTStatusHolder-{}] register new worker: {}", instanceId, address);
return true;
}
3.3 Dispatcher
之前分析过,在Dispatcher的run方法中会获取等待调度器调度的任务,这其中就包含了上面调用map方法生成的子任务。这些子任务最终会包装成HeavyProcessorRunnable线程,调用到其中的innerRun方法:
/**
* HeavyProcessorRunnable:
*/
public void innerRun() throws InterruptedException {
//获取执行处理器
final BasicProcessor processor = processorBean.getProcessor();
String taskId = task.getTaskId();
Long instanceId = task.getInstanceId();
log.debug("[ProcessorRunnable-{}] start to run task(taskId={}&taskName={})", instanceId, taskId, task.getTaskName());
//缓存
ThreadLocalStore.setTask(task);
ThreadLocalStore.setRuntimeMeta(workerRuntime);
// 0. 构造任务上下文
WorkflowContext workflowContext = constructWorkflowContext();
TaskContext taskContext = constructTaskContext();
taskContext.setWorkflowContext(workflowContext);
// 1. 上报执行信息
reportStatus(TaskStatus.WORKER_PROCESSING, null, null, null);
ProcessResult processResult;
ExecuteType executeType = ExecuteType.valueOf(instanceInfo.getExecuteType());
// 2. 根任务 & 广播执行 特殊处理
if (TaskConstant.ROOT_TASK_NAME.equals(task.getTaskName()) && executeType == ExecuteType.BROADCAST) {
// 广播执行:先选本机执行 preProcess,完成后 TaskTracker 再为所有 Worker 生成子 Task
handleBroadcastRootTask(instanceId, taskContext);
return;
}
// 3. 最终任务特殊处理(一定和 TaskTracker 处于相同的机器)
if (TaskConstant.LAST_TASK_NAME.equals(task.getTaskName())) {
handleLastTask(taskId, instanceId, taskContext, executeType);
return;
}
// 4. 正式提交运行
try {
//这里的process方法也就是我们自己写的业务方法
processResult = processor.process(taskContext);
if (processResult == null) {
processResult = new ProcessResult(false, "ProcessResult can't be null");
}
} catch (Throwable e) {
log.warn("[ProcessorRunnable-{}] task(id={},name={}) process failed.", instanceId, taskContext.getTaskId(), taskContext.getTaskName(), e);
processResult = new ProcessResult(false, e.toString());
}
//上报执行结果
reportStatus(processResult.isSuccess() ? TaskStatus.WORKER_PROCESS_SUCCESS : TaskStatus.WORKER_PROCESS_FAILED, suit(processResult.getMsg()), null, workflowContext.getAppendedContextData());
}
innerRun方法我们之前分析过,可以看到,子任务和根任务的执行没有区别,都会最终调用到我们自己写的process方法中来。process方法中会根据isRootTask方法来区分到底是根任务执行还是子任务执行(isRootTask方法就是根据任务名来进行区分的:根任务的任务名写死为OMS_ROOT_TASK,而子任务的任务名为根任务调用map方法传进来的任务名)。
4 reduce方法
在上面第3.3小节的HeavyProcessorRunnable线程的innerRun方法中的第36行代码处,最终任务的执行有特殊处理,我们来看下其实现:
/**
* HeavyProcessorRunnable:
* 处理最终任务
* BROADCAST => {@link BroadcastProcessor#postProcess}
* MAP_REDUCE => {@link MapReduceProcessor#reduce}
*/
private void handleLastTask(String taskId, Long instanceId, TaskContext taskContext, ExecuteType executeType) {
final BasicProcessor processor = processorBean.getProcessor();
ProcessResult processResult;
Stopwatch stopwatch = Stopwatch.createStarted();
log.debug("[ProcessorRunnable-{}] the last task(taskId={}) start to process.", instanceId, taskId);
//从任务表中获取所有子任务的执行结果
List<TaskResult> taskResults = workerRuntime.getTaskPersistenceService().getAllTaskResult(instanceId, task.getSubInstanceId());
try {
switch (executeType) {
case BROADCAST:
if (processor instanceof BroadcastProcessor) {
BroadcastProcessor broadcastProcessor = (BroadcastProcessor) processor;
processResult = broadcastProcessor.postProcess(taskContext, taskResults);
} else {
processResult = BroadcastProcessor.defaultResult(taskResults);
}
break;
case MAP_REDUCE:
if (processor instanceof MapReduceProcessor) {
MapReduceProcessor mapReduceProcessor = (MapReduceProcessor) processor;
//这里就可以看到,最终任务不会执行process方法,而是执行reduce方法
processResult = mapReduceProcessor.reduce(taskContext, taskResults);
} else {
processResult = new ProcessResult(false, "not implement the MapReduceProcessor");
}
break;
default:
processResult = new ProcessResult(false, "IMPOSSIBLE OR BUG");
}
} catch (Throwable e) {
processResult = new ProcessResult(false, e.toString());
log.warn("[ProcessorRunnable-{}] execute last task(taskId={}) failed.", instanceId, taskId, e);
}
TaskStatus status = processResult.isSuccess() ? TaskStatus.WORKER_PROCESS_SUCCESS : TaskStatus.WORKER_PROCESS_FAILED;
//上报执行结果
reportStatus(status, suit(processResult.getMsg()), null, taskContext.getWorkflowContext().getAppendedContextData());
log.info("[ProcessorRunnable-{}] the last task execute successfully, using time: {}", instanceId, stopwatch);
}
在上面第30行代码处就可以看到,最终任务不会执行process方法,而是执行reduce方法,以此来完成最后的汇总过程。而最终任务的创建是在上面第3.1小节的StatusCheckRunnable线程中,定时会去检测是否有最终任务,没有就新建。
5 执行流程
上面说的有点乱(主要不是一条主线完整执行下来,而是有很多的线程异步的执行),总结一下核心的执行流程:
1.在PowerJob控制台中新创建一个MapReduce任务,会持久化到任务表中;
2.服务端启动的时候会开启一些定时任务,其中会找到这些新的待调度的任务(根任务),并执行其中的process业务方法;
3.根任务的process方法中会调用到map方法,将根任务分成多个子任务,持久化到任务表中;
4.这个时候定时任务就会扫到这些子任务,执行其中的process方法;
5.子任务的process方法才是真正的业务处理逻辑;
6.与此同时,还有另外一个定时任务,会定时检查MapReduce任务中的根任务和子任务是否都执行完了。如果都执行完了的话,会查找是否含有最终任务,没有则新建;
7.最终任务也会通过定时任务的扫描去执行,只不过此时执行的是reduce方法,而不是process方法。reduce方法中汇总了所有子任务的执行结果。
原创不易,未得准许,请勿转载,翻版必究