随着对spark的了解,有时会觉得spark就像一个宝盒一样时不时会出现一些难以置信的新功能。每一个新功能被挖掘,就可以使开发过程变得更加便利一点。甚至使很多不可能完成或者完成起来比较复杂的操作,变成简单起来。有些功能是框架专门开放给用户使用,有些则是框架内部使用但是又对外暴露了接口,用户也可以使用的功能。
今天和大家分享的是两个监听器SparkListener和streamingListener,由于这两个监听器的存在使得很多功能的开发变得轻松很多,也使很多技术实现变得轻便很多。
结合我的使用经验,这两个监听器主要可以如下两种用法:
1. 可以获取我们在sparkUI上的任何指标。当你想获取指标并且想做监控预警或者打算重构sparkUI,那你不需要再通过爬虫解析复杂的网页代码就可以获取sparkUI上的各种指标。
2. 对spark任务的各种事件做相应的操作,嵌入回调代码。
比如:你可以在sparkListener中的onApplicationStart方法中做driver端的第三方框架的连接池初始化(连接仅限driver端使用)以及其他变量的初始化,并放置到公共对象中,driver端直接就可以使用。且在onApplicaionComple方法中做连接的释放工作,以及变量的收集持久化操作,以次达到隐藏变量初始化的操作,冰做成公共jar包供其它人使用。
又如:你可以在StreamingListener的onbatchStart操作中获取kafka读取的offset位置以及读取数据条数,在onBatchCompl方法中将这些offset信息保存到mysql/zk中,达到优雅隐藏容错代码的目的。同样可以做成公共jar共其他项目使用。
等等这些都是一些非常酷炫的操作,当然还会有其他的酷炫的使用场景还在等待着你去挖掘。
性能分析
在使用过程中,大家可能比较关系另外一个问题:指标收集,会对流式计算性能产生多大的影响?
答案就是,在指标收集这一块,对于流式计算或者spark core产生的影响会很小。因为即使你不收集SparkUI也会收集,这些指标一样会生成。只是对于driver端的开销会稍微变大,如果在流式计算场景可能需要你调大driver端的cpu和内存
一 .自定义Listener 并注册(伪代码如下):
val spark:SparkSession=null
val ssc:StreamingContext=null
/*注册streamingListnener*/
ssc.addStreamingListener(new MyStreamingListener)
/*注册sparkListener*/
spark.sparkContext.addSparkListener(new MySparkListener)
/*自定义streamingListener*/
class MyStreamingListener extends MyStreamingListener{
//TODO 重载内置方法
}
/*自定义SparkListnener*/
class MySparkListener extends SparkListener {
//TODO 重载内置方法
}
二.SparkListener内置方法讲解
通过如下重写的方法可以发现,你可以通过这个SparkListener对App,job,stage,task,executor,blockManager等产生的指标进实时的收集,并在这些事件触发时嵌入一些代码,可以达到当某些指标达到警戒阈值触发自定义的一些规则。如下已经列出了获取的spark级别指标和事件回调函数,下一节会列出streaming的性能监控点以及和收集的指标。
class MySparkListener extends SparkListener {
/*当整个应用开始执行时*/
override def onApplicationStart(applicationStart: SparkListenerApplicationStart): Unit={
/*获取appID-->spark on yarn模式下的appID一致*/
applicationStart.appId
/*appName*/
applicationStart.appName
/*driver端的日志,如果配置了日志的截断机制,获取的将不是完整日志*/
applicationStart.driverLogs
/*提交的用户*/
applicationStart.sparkUser
/*开始的事件*/
applicationStart.time
}
/*当整个Application结束时调用的回调函数*/
override def onApplicationEnd(applicationEnd: SparkListenerApplicationEnd): Unit = {
/*结束的时间*/
applicationEnd.time
}
/*当job开始执行时触发的回调函数*/
override def onJobStart(jobStart: SparkListenerJobStart): Unit = {
/*jobId和SParkUI上的一致*/
jobStart.jobId
/*配置信息*/
jobStart.properties
/*当前job根据宽窄依赖生成的所有strageID*/
jobStart.stageIds
/*job开始的时间*/
jobStart.time
jobStart.properties
/*当前job每一个stage的信息抽象*/
jobStart.stageInfos.foreach(stageInfo => {
stageInfo.stageId
/*stage提交的时间,不是task开始执行的时间,这个时间是stage开始抽象成taskDesc的开始时间*/
stageInfo.submissionTime
/*这个stage完成的时间*/
stageInfo.completionTime
/*当前stage发成了错误会重试,重试会在stageID后加上“_重试次数”*/
stageInfo.attemptI