最近需要实现这样一种功能,利用调度器将spark应用提交到yarn上执行,然后获取最后的执行状态。但是发现网上相关的案例非常少,大都让我们去看日志,显然在生产环境中这种做法是不合理的。
在网上找了半天,发现了一种利用yarn去监控应用的方案。所以我实现了第一种方案,获取这个应用的applicationId,就可以通过applicationId获取应用的状态,然后写一个子线程,不断去轮询获取yarn上指定应用的状态。
代码如下:
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.yarn.api.records.ApplicationId;
import org.apache.hadoop.yarn.api.records.ApplicationReport;
import org.apache.hadoop.yarn.api.records.YarnApplicationState;
import org.apache.hadoop.yarn.client.api.YarnClient;
import org.apache.hadoop.yarn.exceptions.YarnException;
import org.apache.hadoop.yarn.util.ConverterUtils;
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis;
/**
* @Description yarn应用监控
*
* @author Suntz
* @Date 2021-3-23
*
*/
@Slf4j
public class YarnMonitorComponent extends Thread {
private ApplicationId applicationId;
public YarnMonitorComponent(String appId) {
log.info("==================================");
log.info("应用ID是:" + appId);
log.info("==================================");
this.applicationId = ConverterUtils.toApplicationId(appId);
}
@Override
public void run() {
while (true) {
if (getState(applicationId).equals(YarnApplicationState.FINISHED)) {
log.info("==================================");
log.info("执行状态:应用执行成功");
log.info("==================================");
}
if (getState(applicationId).equals(YarnApplicationState.FAILED)) {
log.info("==================================");
log.info("执行状态:应用执行失败");
log.info("==================================");
}
if (getState(applicationId).equals(YarnApplicationState.KILLED)) {
log.info("==================================");
log.info("应用被终止");
log.info("==================================");
}
}
}
private YarnApplicationState getState(ApplicationId applicationId) {
YarnClient client = YarnClient.createYarnClient();
Configuration conf = new Configuration();
client.init(conf);
client.start();
// ApplicationId appId = ConverterUtils.toApplicationId(appId);
YarnApplicationState yarnApplicationState = null;
try {
ApplicationReport applicationReport = client.getApplicationReport(applicationId);
yarnApplicationState = applicationReport.getYarnApplicationState();
} catch (YarnException | IOException e) {
log.error("监控出错", e);
}
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
return yarnApplicationState;
}
}
调用如下:
YarnMonitorComponent yarnMonitorComponent = new YarnMonitorComponent(
sparkContext.applicationId());
ExecutorService pool = Executors.newSingleThreadExecutor();
pool.execute(yarnMonitorComponent);
但是这种实现方案有一个问题,就是spark运行失败,但是yarn日志仍然显示成功,这个也不知道到底什么原因,大概是因为只要yarn主程序流程走完就是成功状态。
于是找了半天,又发现了SparkListener。实现SparkListener接口,重写相关方法,代码如下:
@Slf4j
public class SparkAppListener extends SparkListener {
public SparkAppListener() {
}
@Override
public void onTaskEnd(SparkListenerTaskEnd taskEnd) {
if (!taskEnd.reason().toString().equals("Success")) {
log.info("task运行失败");
} else {
log.info("task运行成功");
}
}
@Override
public void onApplicationEnd(SparkListenerApplicationEnd applicationEnd) {
log.info("应用运行完成");
}
}
在sparkContext中注册这个监听器,这样当有task结束,或者应用结束就会调用相关方法。
sparkContext().addSparkListener(new SparkAppListener());
但是应用结束无法获取应用结束的原因或者状态,只知道应用结束,不知道应用结束的状态是成功还是失败,这真的是一个大遗憾……大概是分布式环境下这个工作不好做吗?task结束可以后可以获取task结束的原因,但是应用结束无法获取。
所以我现在就只能通过task来间接监控,只要有一个task失败,就认为整个应用失败。这对于监控整个应用来说不太合理。但是我也没发现其他的办法。
各位如果有好的方案可以私我,或者评论,大家一起讨论,如果我有好的方案也会及时补充的。
注意以上,均需要将yarn-site.xml拷贝到resource目录下。