前言
本文基于structured streaming开展。spark应用部署后因集群资源不足等问题导致应用出现‘卡住’现象,如下图所示。我们在应用部署后不可能时时关注应用的运行状况,应用可能卡住一晚、一天甚至更长时间,那么及时发现应用‘卡住’现象,排查卡住原因,也是很有必要的。
参考
1.spark官网提供了Monitoring Streaming Queries板块。有两种用于监视和调试active query的方式:interactively 和asynchronously,并给出了代码实现。我这里主要以asynchronously实现监控。
2.Spark2.2(三十九):如何根据appName监控spark任务,当任务不存在则启动(任务存在当超过多久没有活动状态则kill,等待下次启动) 该文章中实现卡住监控也是围绕asynchronously实现的,将query的实时状态信息写入hdfs,通过脚本监控达到应用重启动。
思路
考虑到业务场景,因spark应用不止一个,且每个应用不止一个query,期待当发现应用卡住的时候,可以进行告警提示,从而场景复现,及时排查问题。我的实现:将query的实时状态信息写入hbase,写一个服务定时查看hbase,当当前时间与query的状态信息超过一个阈值(比如15min)即告警。
实现
hbase表设计:rowkey:应用名称;列簇:info;列:appId(其value为该应用运行后的appId),queryIds(其value为该应用所有的queryId组合),queryId(其value为该query的实时活动时间)
说明:为什么hbase中会考虑存储queryId?因为结合structured streaming的checkpoint机制,自应用启动后,queryId会保持不变,即使应用因某种原因导致中断再次重新启动,该queryId也是不变的。参考说明:
监听代码
public class Main {
public static void main(String[] args) throws StreamingQueryException {
SparkSession sparkSession = SparkSession.builder()
.appName("sparkTest")
.config("spark.sql.warehouse.dir", "./spark-warehouse")
.master("local[*]")
.getOrCreate();
sparkSession.sparkContext().setLogLevel("warn");
//监听
sparkSession.streams().addListener(new QueryListener(sparkSession));
//流处理,比如:消费kafka数据打印在控制台
......
query.awaitTermination();
query2.awaitTermination();
}
}
public class QueryListener extends StreamingQueryListener {
private SparkSession sparkSession = null;
private Table table = null;
private StringBuffer queryIds = new StringBuffer();
public QueryListener(SparkSession sparkSession){
this.sparkSession = sparkSession;
}
@Override
public void onQueryStarted(StreamingQueryListener.QueryStartedEvent queryStarted) {
queryIds.append(queryStarted.id().toString()+"***");
}
@Override
public void onQueryTerminated(StreamingQueryListener.QueryTerminatedEvent queryTerminated) {
......
}
@Override
public void onQueryProgress(StreamingQueryListener.QueryProgressEvent queryProgress) {
String appId = this.sparkSession.conf().get("spark.app.id");
String appName = this.sparkSession.conf().get("spark.app.name");
String queryId = queryProgress.progress().id().toString();
long time = new Date().getTime();
......
Put put = new Put(Bytes.toBytes(appName));
put.addColumn("info".getBytes(), ("appId").getBytes(), appId.getBytes());
put.addColumn("info".getBytes(), ("queryIds").getBytes(), queryIds.toString().getBytes());
put.addColumn("info".getBytes(), queryId.getBytes(), String.valueOf(time).getBytes());
table.put(put);
......
}
}
按照上述实现,就可以实时将应用的query时间写入hbase,然后再写个定时服务,监听hbase的info:{queryId},可以获取指定应用某一query的时间,判断该时间距离当前时间的差值,若超过自己设定的阈值则告警。