1、起因是使用azkaban配置邮件时,发现邮件不起作用。当时在azkaban-web/conf/azkaban.properties 文件中添加了如下配置
# mail settings
mail.sender=ttttt@autohome.com.cn
mail.host=114.114.114.114
mail.user=ttttt@autohome.com.cn
mail.password=xxxxxx
job.failure.email=xxxxx@autohome.com.cn
job.success.email=xxxxx@autohome.com.cn
但是启动后运行任务,发现不起作用。(其实还应该在job文件里配置邮箱列表,可是当时看官方文档没注意。)于是开始阅读源码,想找到azkaban的邮件发送代码。
2、找代码
把azkaban2.5的代码导入到idea后,先思考是否有测试邮件的代码。顺利找到。
运行测试代码后,发现邮件能顺利发送出去。看来不是邮箱配置问题。继续找。
打开EmailMessage.sendEmail()方法。如果有发送邮件的功能,是肯定要调用这个方法的。于是继续找。
找到调用sendEmail()的方法。选择Emailer.sendSuccessEmail(ExecutableFlow)
可以看到这是一个run()方法,然后找到启动这个线程的start方法。
public ExecutorManager(Props props, ExecutorLoader loader, Map<String, Alerter> alters) throws ExecutorManagerException {
this.executorLoader = loader;
this.loadRunningFlows();
executorHost = props.getString("executor.host", "localhost");
executorPort = props.getInt("executor.port");
alerters = alters;
cacheDir = new File(props.getString("cache.directory", "cache"));
executingManager = new ExecutingManagerUpdaterThread();
<span style="color:#ff0000;">executingManager.start();</span>
long executionLogsRetentionMs = props.getLong("execution.logs.retention.ms", DEFAULT_EXECUTION_LOGS_RETENTION_MS);
cleanerThread = new CleanerThread(executionLogsRetentionMs);
cleanerThread.start();
}
3、阅读main方法
main(){
app = new AzkabanWebServer(server, azkabanSettings);
}
AzkabanWebServer(){
//创建邮箱对象并放入map中
alerters = loadAlerters(props);
//加载执行任务管理相关对象
executorManager = loadExecutorManager(props);
}
private Map<String, Alerter> loadAlerters(Props props) {
Map<String, Alerter> allAlerters = new HashMap<String, Alerter>();
// load built-in alerters
Emailer mailAlerter = new Emailer(props);
allAlerters.put("email", mailAlerter);
// load all plugin alerters
String pluginDir = props.getString("alerter.plugin.dir", "plugins/alerter");
allAlerters.putAll(loadPluginAlerters(pluginDir));
return allAlerters;
}
private ExecutorManager loadExecutorManager(Props props) throws Exception {
JdbcExecutorLoader loader = new JdbcExecutorLoader(props);
ExecutorManager execManager = new ExecutorManager(props, loader, alerters);
return execManager;
}
创建数据库loader,并创建ExecutorManager对象。
public ExecutorManager(Props props, ExecutorLoader loader, Map<String, Alerter> alters) throws ExecutorManagerException {
this.executorLoader = loader;
//从数据库查询得到正在运行的flows
this.loadRunningFlows();
//得到executor的IP和port
executorHost = props.getString("executor.host", "localhost");
executorPort = props.getInt("executor.port");
//邮件对象在这里
alerters = alters;
cacheDir = new File(props.getString("cache.directory", "cache"));
//开启多线程,对任务更新进行处理
executingManager = new ExecutingManagerUpdaterThread();
executingManager.start();
//开始线程,执行清理
long executionLogsRetentionMs = props.getLong("execution.logs.retention.ms", DEFAULT_EXECUTION_LOGS_RETENTION_MS);
cleanerThread = new CleanerThread(executionLogsRetentionMs);
cleanerThread.start();
}
查看ExecutingManagerUpdaterThread的run方法
public void run() {
while(!shutdown) {
try {
lastThreadCheckTime = System.currentTimeMillis();
updaterStage = "Starting update all flows.";
logger.info("runningFlows:"+runningFlows.size());
<span style="color:#ff0000;">Map<ConnectionInfo, List<ExecutableFlow>> exFlowMap = getFlowToExecutorMap();</span>
ArrayList<ExecutableFlow> finishedFlows = new ArrayList<ExecutableFlow>();
ArrayList<ExecutableFlow> finalizeFlows = new ArrayList<ExecutableFlow>();
if (exFlowMap.size() > 0) {
for (Map.Entry<ConnectionInfo, List<ExecutableFlow>> entry: exFlowMap.entrySet()) {
List<Long> updateTimesList = new ArrayList<Long>();
List<Integer> executionIdsList = new ArrayList<Integer>();
ConnectionInfo connection = entry.getKey();
updaterStage = "Starting update flows on " + connection.getHost() + ":" + connection.getPort();
// We pack the parameters of the same host together before we query.
fillUpdateTimeAndExecId(entry.getValue(), executionIdsList, updateTimesList);
Pair<String,String> updateTimes = new Pair<String, String>(
ConnectorParams.UPDATE_TIME_LIST_PARAM,
JSONUtils.toJSON(updateTimesList));
Pair<String,String> executionIds = new Pair<String, String>(
ConnectorParams.EXEC_ID_LIST_PARAM,
JSONUtils.toJSON(executionIdsList));
Map<String, Object> results = null;
try {
results = callExecutorServer(connection.getHost(), connection.getPort(), ConnectorParams.UPDATE_ACTION, null, null, executionIds, updateTimes);
} catch (IOException e) {
logger.error(e.getMessage(),e);
for (ExecutableFlow flow: entry.getValue()) {
Pair<ExecutionReference, ExecutableFlow> pair = runningFlows.get(flow.getExecutionId());
updaterStage = "Failed to get update. Doing some clean up for flow " + pair.getSecond().getExecutionId();
if (pair != null) {
ExecutionReference ref = pair.getFirst();
int numErrors = ref.getNumErrors();
if (ref.getNumErrors() < this.numErrors) {
ref.setNextCheckTime(System.currentTimeMillis() + errorThreshold);
ref.setNumErrors(++numErrors);
}
else {
logger.error("Evicting flow " + flow.getExecutionId() + ". The executor is unresponsive.");
//TODO should send out an unresponsive email here.
finalizeFlows.add(pair.getSecond());
}
}
}
}
// We gets results
if (results != null) {
List<Map<String,Object>> executionUpdates = (List<Map<String,Object>>)results.get(ConnectorParams.RESPONSE_UPDATED_FLOWS);
for (Map<String,Object> updateMap: executionUpdates) {
try {
ExecutableFlow flow = updateExecution(updateMap);
updaterStage = "Updated flow " + flow.getExecutionId();
if (isFinished(flow)) {
finishedFlows.add(flow);
finalizeFlows.add(flow);
}
} catch (ExecutorManagerException e) {
ExecutableFlow flow = e.getExecutableFlow();
logger.error(e);
if (flow != null) {
logger.error("Finalizing flow " + flow.getExecutionId());
finalizeFlows.add(flow);
}
}
}
}
}
updaterStage = "Evicting old recently finished flows.";
evictOldRecentlyFinished(recentlyFinishedLifetimeMs);
// Add new finished
for (ExecutableFlow flow: finishedFlows) {
if(flow.getScheduleId() >= 0 && flow.getStatus() == Status.SUCCEEDED){
ScheduleStatisticManager.invalidateCache(flow.getScheduleId(), cacheDir);
}
fireEventListeners(Event.create(flow, Type.FLOW_FINISHED));
recentlyFinished.put(flow.getExecutionId(), flow);
}
updaterStage = "Finalizing " + finalizeFlows.size() + " error flows.";
// Kill error flows
for (ExecutableFlow flow: finalizeFlows) {
<span style="color:#ff0000;">finalizeFlows(flow);</span>
}
}
updaterStage = "Updated all active flows. Waiting for next round.";
synchronized(this) {
try {
if (runningFlows.size() > 0) {
this.wait(waitTimeMs);
}
else {
this.wait(waitTimeIdleMs);
}
} catch (InterruptedException e) {
}
}
}
catch (Exception e) {
logger.error(e);
}
}
}
有个细节:在ExecutorManager类中,有个submitExecutableFlow方法,任务提交的时候,会执行这个方法,在这个方法中更新runningFlows
在finalizeFlows()方法中,会发送邮件。
最后在条件判断中,终于找到了答案。原来发送邮件,需要job里添加配置。
type=command
command=/opt/azkaban/script/sqoop_bdm_usedcrm_dealerfallareaflags/hive_create_partition02.sh ${job_date} ${db_name} ${table_name}
dependencies=01sqoop
success.emails=xxxxxx@autohome.com.cn
retries=3
retry.backoff=20000
这样才会发送邮件。