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.對哪些日誌具體指標監控,羅列出來;例如個別方式耗時,特定字段告警等;
原創不易,謝絕白嫖,好的話就點個贊吧!