近段时间发现好多分析的mr作业延迟1个小时到2个小时,其实那个作业平时可能会只需要20分钟。分析作业状态发现延迟是在job的cleanup阶段。
近段时间由于用户的增长及数据的持续飙升,集群作业越来越多,每个作业占用槽位也不断增长,导致集群槽位紧张,所以集群出现排队现象本来运算正常,但是如果整个作业setup、map、reduce都处理完了,仅剩cleanup(极其轻量)没执行完导致延迟1-2小时,这个太也能搞!
看下某个作业状态数据(注:这个作业很小,1-2分钟执行完毕):
可以看到setup\map\reduce 在12:36到12::37之间执行完毕,但是cleanup却在14:31分才启动,而执行也只用了8s!
也就是说为了这8s,该作业完成等待了将近2个小时!
看下JOBTRACKER日志:
12:39:06,580 INFO org.apache.hadoop.mapred.JobTracker: Adding task (JOB_CLEANUP) 'attempt_201212261519_3151096_m_000002_0' to tip task_201212261519_3151096_m
_000002, for tracker ...
说明mr执行完毕后即分配了task并提交到了tt.
在tt上查看日志搜索 该attampt.
14:31:45,064 INFO org.apache.hadoop.mapred.TaskTracker: Trying to launch : attempt_201212261519_3151096_m_000002_0 which needs 1 slots
说明该task一直在tt上等待。下面看下相关代码:
在类org.apache.hadoop.mapred.TaskTracker 的run()方法里:
try {
State osState = offerService();
if (osState == State.STALE) {
staleState = true;
} else if (osState == State.DENIED) {
denied = true;
}
} catch (Exception ex) {
...
}
其中红色标出的为tt提供服务的主要方法,在该方法里有:
TaskTrackerAction[] actions = heartbeatResponse.getActions();
if (action instanceof LaunchTaskAction) {
addToTaskQueue((LaunchTaskAction)action);
} else if (action instanceof CommitTaskAction) {
...
}
通过心跳获取到jt的指令后即处理指令。对于LaunchTaskAction即新task加入taskqueue,代码如下:
private void addToTaskQueue(LaunchTaskAction action) {
if (action.getTask().isMapTask()) {
mapLauncher.addToTaskQueue(action);
} else {
reduceLauncher.addToTaskQueue(action);
}
}
如果是map的话加入mapLauncher,reduce加入reduceLauncher。而 一般的cleanup都会是map。
这里的mapLauncher和reduceLauncher是tt启动时初始化的2个线程:
mapLauncher = new TaskLauncher(TaskType.MAP, maxMapSlots);
reduceLauncher = new TaskLauncher(TaskType.REDUCE, maxReduceSlots);
mapLauncher.start();
reduceLauncher.start();
加入队列即加入一个链表里。这是线程定义时初始化链表
public TaskLauncher(TaskType taskType, int numSlots) {
this.maxSlots = numSlots;
this.numFreeSlots = new IntWritable(numSlots);
this.tasksToLaunch = new LinkedList<TaskInProgress>();
setDaemon(true);
setName("TaskLauncher for " + taskType + " tasks");
}
加入链表
public void addToTaskQueue(LaunchTaskAction action) {
synchronized (tasksToLaunch) {
TaskInProgress tip = registerTask(action, this);
tasksToLaunch.add(tip);
tasksToLaunch.notifyAll();
}
}
线程随TT启动,实时去查询链表,如果链表存在则取出第一个!
//get the TIP
tip = tasksToLaunch.remove(0);
task = tip.getTask();
LOG.info("Trying to launch : " + tip.getTask().getTaskID() +
" which needs " + task.getNumSlotsRequired() + " slots");
通过比对日志,
所有分配到tt的map作业确实也是根据先后顺序去处理的。但这导致了开头的问题!
优化方案: 建议cleanup与其他map或reduce区分开来,cleanup的优先级应高于其他map处理作业!
可能有些地方考虑欠妥,欢迎批评指正