spark监控streamingListener使用与监控告警

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.对哪些日志具体指标监控,罗列出来;例如个别方式耗时,特定字段告警等;


原创不易,谢绝白嫖,好的话就点个赞吧!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章