一次azkaban源码阅读经历

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();
		
	}


然后找调用ExecutorManager构造方法的方法。



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

这样才会发送邮件。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值