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.對哪些日誌具體指標監控,羅列出來;例如個別方式耗時,特定字段告警等;


原創不易,謝絕白嫖,好的話就點個贊吧!

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