Spark-steaming监控设计与验证方案
created by fangchangtan | 2020/2/24
原创不易,谢绝白嫖,好的话就点个赞吧!
1总览
1.1参考文献:
- 对spark steaming运行中的批处理过程的监控
https://blog.csdn.net/qq475781638/article/details/90241761
https://blog.csdn.net/qq_14950717/article/details/79446182
包含对每一个批处理的开始时间,结束时间,执行耗时、执行数据量等输出日志
- 对spark运行过程中的jvm内存、cpu负载进行监控
https://blog.csdn.net/lsshlsw/article/details/82670508?spm=a2c6h.13066369.0.0.3a22568d3MhOyz
运行时 Executor JVM 的状态信息的监控
Event spike 示例:
(参考:https://blog.csdn.net/Gamer_gyt/article/details/53381279?utm_source=blogxgwz8 )
1.2 问题场景
需要监控部署的spark steaming程序健康运行。第一,主要是通过对日志中speak steaming的心跳信息进行侦测,保证程序不挂掉(或及时切换重启);第二,对某些批次执行时间过长做出告警;
其实该问题的重点在于,淌通第一个问题之后,第二个问题就迎刃而解了。
1.3 总体验证思路
通过改造spark中加入steamingListener接口代码添加spark作业逻辑,然后使用logstash过滤挂件词【batchSubmitted】,将该该结果存入elasticsearch库的index-spark-heatbeat-*{时间天},然后使用elasticalert只用侦听到该关键词,统计固定时间的词频确定告警规则;
全部流程如下图所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-trUPGilm-1583760760645)(E:\typora-workspace\typora-picture\sparksteaming1.jpg)]
验证场景问题分解:
第一:场景1 需要验证spark提供的接口streamingListener的功能;
第二:场景2 需要演杨正logstash的过滤规则,即怎样同一份日志输出到不同的多个索引中;
第三:场景3 elasticalert的告警规则-spike 毛刺状态作为告警触发规则
2 详细设计与验证
2.1 验证场景1
2.1.1问题场景:
streaming Listener接口提供的,心跳问题是可以可以持续监控。
当前在本地测试该功能,确定使用StreamingListener接口实现该功能。
2.1.2 技术思路:
使用spark调研之后,决定采用spark自带的接口StreamingListener实现心跳检测的功能;
package swtx;
import org.apache.spark.streaming.scheduler.*;
public class SparkMonitoringListener implements StreamingListener {
private String appName ;
private int duration;
public SparkMonitoringListener(String appName,int duration){
this.appName = appName;
this.duration = duration;
};
/**
* 当该spark程序启动的时候,出发执行
*/
@Override
public void onStreamingStarted(StreamingListenerStreamingStarted streamingListenerStreamingStarted) {
System.out.println("【onStreamingStarted】: "+"++++++++++++++++++");
}
/**
*
* @param streamingListenerReceiverStarted
*/
@Override
public void onReceiverStarted(StreamingListenerReceiverStarted streamingListenerReceiverStarted) {
System.out.println("【onReceiverStarted】: "+"------------------");
}
@Override
public void onReceiverError(StreamingListenerReceiverError streamingListenerReceiverError) {
}
@Override
public void onReceiverStopped(StreamingListenerReceiverStopped streamingListenerReceiverStopped) {
}
/**
* 当每一个批次(目前ML是1min)提交的时候,触发方法执行。
* 这个方式只要ml在运行,就会一直触发执行,相当于心跳的功能。
*
* @param batchSubmitted
*/
@Override
public void onBatchSubmitted(StreamingListenerBatchSubmitted batchSubmitted) {
System.out.println("【 batchSubmitted 】"+"************");
}
/**
* 某一批次批次开始执行时候,触发该方法
* @param batchStarted
*/
@Override
public void onBatchStarted(StreamingListenerBatchStarted batchStarted) {
System.out.println(appName+ ">>> Batch started...records in batch = " + batchStarted.batchInfo().numRecords());
}
/**
* 某一批次数据结束执行时候,触发该方法
* @param batchCompleted
*/
@Override
public void onBatchCompleted(StreamingListenerBatchCompleted batchCompleted) {
Object start = batchCompleted.batchInfo().processingStartTime().get();
Object end = batchCompleted.batchInfo().processingEndTime().get();
Object batchTime = batchCompleted.batchInfo().batchTime();
Object numRecords = batchCompleted.batchInfo().numRecords();
long takeTime =Long.valueOf(String.valueOf(end)).longValue()-Long.valueOf(String.valueOf(start)).longValue();
System.out.println(appName + "batch finished! start: "+start+" | end :"+end+" | takeTime: "+takeTime+" | batchTime: "+batchTime.toString()+" | numRecords: "+numRecords);
}
@Override
public void onOutputOperationStarted(StreamingListenerOutputOperationStarted streamingListenerOutputOperationStarted) {
}
@Override
public void onOutputOperationCompleted(StreamingListenerOutputOperationCompleted streamingListenerOutputOperationCompleted) {
}
}
主函数主要工作:
//设置SparkConf,配置连接资源信息.local[2]表示本地模式两个core
SparkConf conf = new SparkConf().setMaster("local[1]")
.set("spark.testing.memory", "512000000")
.setAppName(appName);
//设置批次时间,测试可以用5秒,创建StreamingContext对象,运行时需要
JavaStreamingContext streamingContext = new JavaStreamingContext(conf, Durations.seconds(5));
//添加监控:只用在spark主函数中加入这段代码,即可提供spark批次执行状态的输出;
streamingContext.addStreamingListener(new SparkMonitoringListener(appName,0));
控制台输出日志结果如下:
20/03/03 12:23:37 INFO AppInfoParser: Kafka commitId : a7a17cdec9eaa6c5
20/03/03 12:23:37 INFO CachedKafkaConsumer: Initial fetch for spark-executor-fang_id22 test_tank007 1 452
20/03/03 12:23:37 INFO AbstractCoordinator: Discovered coordinator 172.19.32.73:9392 (id: 2147483645 rack: null) for group spark-executor-fang_id22.
value:3
【 batchSubmitted 】************
20/03/03 12:23:40 INFO JobScheduler: Added jobs for time 1583209420000 ms
20/03/03 12:23:45 INFO JobScheduler: Added jobs for time 1583209425000 ms
【 batchSubmitted 】************
20/03/03 12:23:50 INFO JobScheduler: Added jobs for time 1583209430000 ms
【 batchSubmitted 】************
20/03/03 12:23:55 INFO JobScheduler: Added jobs for time 1583209435000 ms
【 batchSubmitted 】************
20/03/03 12:24:00 INFO JobScheduler: Added jobs for time 1583209440000 ms
【 batchSubmitted 】************
**输出结果:**模拟spark中由于逻辑算法或者资源不足造成task卡住,进而导致job卡住的现象。从如下结果可以看出,StreamingListener接口中的特定方法onBatchSubmitted()在job卡住的情况下,仍然会继续执行。(即可说明:该方法可以作为该spark streaming应用是否存活的心跳信息)
2.2 问题场景2
2.2.1 验证场景
需要演验证logstash的过滤规则,即怎样同一份日志输出到不同的多个索引中,以方便将业务日志和心跳日志区分开来,方便维护和查找定位;
2.2.2技术思路:
对应在172.19.32.167上启动该logstash。
模拟的logstash的docker容器(模拟2【框架心跳日志】)
docker run \
--rm \
--name fct-alert-logstash \
-p 5047:5044 \
-v /root/fct/logstash-test-spark/logstash_kafka.conf:/logstash/logstash_kafka.conf \
-v /root/fct/logstash-test-spark/logstash.yml:/usr/share/logstash/config/logstash.yml \
registry.marathon.l4lb.thisdcos.directory:5000/logstash:6.6.1 \
logstash -f /logstash/logstash_kafka.conf
其中,使用的logstash_kafka.conf的配置文件如下:(其中我们对spark streaming中的同一份日志根据不同的目的,输出到不同的es索引)
input {
kafka{
bootstrap_servers => ["192.19.32.69:9392,192.19.32.71:9392,192.19.32.73:9392"]
client_id => "client-test111-fct2"
group_id => "groupid-tank007-test-fct"
auto_offset_reset => "earliest"
consumer_threads => 6
decorate_events => true
topics => ["topic-tank007"]
type => "spark-listener"
}
}
filter{
if [type] == "spark-listener"{
csv{
columns=>["param0","time","param2","param3"]
separator=>"@#"
}
mutate{
convert => ["param3","float"]
}
}
}
output {
if [param0] == "key1"{
stdout{
codec=> rubydebug
}
elasticsearch {
hosts => ["192.19.32.106:9202","192.19.32.131:9202","192.19.32.141:9202"]
index => "fct-logstash-key1_%{+YYYY-MM-dd}"
}
} else {
stdout{
codec=> rubydebug
}
elasticsearch {
hosts => ["192.19.32.106:9202","192.19.32.131:9202","192.19.32.141:9202"]
index => "fct-logstash-key2_%{+YYYY-MM-dd}"
}
}
}
注释说明:如果以param0字段作为业务日志和系统框架日志的区分,其中key1表示spark框架心跳日志,key2表示业务日志,分别输出到不同的索引中fct-logstash-key1_%{+YYYY-MM-dd}和fct-logstash-key2_%{+YYYY-MM-dd}。
启动logstash之后,从logstash的日志中可以看到对应的日志输出
框架心跳日志(模拟2):
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-poZfejbv-1583760760649)(C:\Users\Administrator\AppData\Local\Temp\1583680853124.png)]
目前是,已经可以将spark的日志输出到不同的kafka的topic中(好处是可以区分不同的来源日志数据);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rp1FGQKa-1583760760651)(E:\typora-workspace\typora-picture\1583725225660.png)]
**结论:**可以将同一份日志(包含业务日志和框系统心跳日志),通过区分字段,输出到es中的不同索引中;
细节注意事项:
1.spark steaming需要将日志分别输出到两个topic中;(重点,是否可以实现);目前的业务和框架心跳日志在一起的方式,即混在一起的方式,受限于logstash的处理能力,可能有干扰的情况,对心跳检测可能不利;
2.3 验证3 elasticalert的心跳告警规则
2.3.1 验证问题
elasticalert对心跳告警。需要做到正常心跳不告警,启动时候不告警,长时间停止也不告警,只有在停止的时候发出一条告警规则;
第一步:logstash的同一个服务按照不同的过滤规则分别输出多个索引中;
第二步:确定logstash输出到索引后,elasticalert的告警(发邮件为准);
第三步:确定开始启动监控,停止监控后不再发送邮件(或者间隔12小时重发一次);
2.3.2 技术思路
模拟向kafka中发送数据,logstash接受topic数据过滤之后输出到es中的特定索引(专属spark框架的索引)中,然后elasticalert监控es中索引,对关键词词频(单位时间窗口中的事件数),只有发生波动(词频上升|下降,二选一)时候,发挥发出邮件告警通知;
优先,来看一下elasticalert的常规报警规则如下:
#配置一种数据验证的方式,有 any,blacklist,whitelist,change,frequency,spike,flatline,new_term,cardinality
#any:只要有匹配就报警;
#blacklist:compare_key字段的内容匹配上 blacklist数组里任意内容;
#whitelist:compare_key字段的内容一个都没能匹配上whitelist数组里内容;
#change:在相同query_key条件下,compare_key字段的内容,在 timeframe范围内 发送变化;
#frequency:在相同 query_key条件下,timeframe 范围内有num_events个被过滤出 来的异常;
#flatline:timeframe 范围内,数据量小于threshold 阈值;
#new_term:fields字段新出现之前terms_window_size(默认30天)范围内最多的terms_size (默认50)个结果以外的数据;
#cardinality:在相同 query_key条件下,timeframe范围内cardinality_field的值超过 max_cardinality 或者低于min_cardinality
## (Required)
## Type of alert.
## the frequency rule type alerts when num_events events occur with timeframe time
##示例:我配置的是frequency,这个需要两个条件满足,在相同 query_key条件下,timeframe 范围内有num_events个被过滤出来的异常
type: frequency
了解完告警规则之后,就可以开始编写自己的告警规则了(有关elasticalert的详细使用方式,参看另外一篇文章《docker 安装、配置、验证ElasticAlert》)
Event spike 示例:
(参考:https://blog.csdn.net/Gamer_gyt/article/details/53381279?utm_source=blogxgwz8 )
本验证中(在106主机上验证),配置使用的elasticalert的告警规则配置文件spark-listener-rules.yaml,如下
# 可选的,elasticsearch 所在主机的ip地址
es_host: 192.19.32.106
# 可选的,elasticsearch的服务端口
# 这里的elasticsearch之所以说是可选是因为在config.ymal中已经配置过了
es_port: 9202
#rule name 必须是独一的,不然会报错,这个定义完成之后,会成为报警邮件的标题
## (Required)
## Rule name, must be unique
name: fct-spark-rule-name-Event-spike
# 可选,是否使用SSL加密传送数据,这是因为elasticsearch如果被search-guard或者x-pack/shield保护的话,要启用
#use_ssl: True
# 可选,对应访问elasticsearch的账号和密码
#es_username: someusername
#es_password: somepassword
# 必选的,rule的类型
type: spike
#这个index 是指再kibana 里边的index,支持正则匹配,支持多个index,同时如果嫌麻烦直接* 也可以。
## (Required)
## Index to search, wildcard supported
# 必选的,监控的索引
index: fct-logstash*
# 二选一,cur:当前窗口,ref:参照窗口 后边会着重讲述
threshold_cur: 3
# #threshold_ref: 5
##统计词频是才有用,此处可能无用
num_events: 1
# 必选的,窗口参照时间
timeframe:
minutes: 1
# 必选的,衡论是否进行报警的一个警戒线
spike_height: 1
# 必选的,spike_type有三种类型,up,down,both
spike_type: "up"
# 必须的,进行匹配过滤的条件
# 更多过滤条件设置参考: #http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl.html
filter:
- query:
query_string:
query: "param0:key1"
#在邮件正文会显示你定义的alert_text
alert_text: "你好,请回复邮件,fff"
# Setup report smtp config
smtp_host: smtp.163.com
smtp_port: 25
smtp_ssl: False
#SMTP auth
from_addr: [email protected]
email_reply_to: [email protected]
smtp_auth_file: /opt/elastalert/config/smtp_auth.yaml
# 必选的 报警方式
alert:
- "email"
# (required, email specific)
# 必选的,邮件的接受地址
email:
- "[email protected]"
最终,163邮箱收到的告警的消息,如下所示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2Id0noi8-1583760760654)(E:\typora-workspace\typora-picture\1583720879287.png)]
告警邮件内容解析:
- 可以看到邮件的名称是spark-listener-rules.yaml告警规则中的name字段。
- 在邮件正文会显示你定义的alert_text,“你好,请回复邮件,方昌坦”
- 其次,elasticalert告警的触发的原因描述;
- 最后是,触发的消息体内容详情
注意事项:
1.elasticalert已经实现频率报警,是否能实现频率变化(降低)报警,其中升高已验证;
2.总体规划中的对批次执行时间(实际耗时),时间过长执行报警通知,是否有必要;
3.对哪些日志具体指标监控,罗列出来;例如个别方式耗时,特定字段告警等;
原创不易,谢绝白嫖,好的话就点个赞吧!