近段時間發現好多分析的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處理作業!
可能有些地方考慮欠妥,歡迎批評指正