SparkStreaming updateStateByKey案例實戰和內置源碼解密
1、sparkStreaming中的updateStateByKey案例實戰
2、sparkStreaming中的updateStateByKey源碼解密
package com.tom.spark.SparkApps.sparkstreaming;
import java.util.Arrays;
import java.util.List;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.function.FlatMapFunction;
import org.apache.spark.api.java.function.Function2;
import org.apache.spark.api.java.function.PairFunction;
import org.apache.spark.streaming.Durations;
import org.apache.spark.streaming.api.java.JavaDStream;
import org.apache.spark.streaming.api.java.JavaPairDStream;
import org.apache.spark.streaming.api.java.JavaReceiverInputDStream;
import org.apache.spark.streaming.api.java.JavaStreamingContext;
import scala.Tuple2;
import com.google.common.base.Optional;
/**
* UpdateStateByKey的主要功能是可以隨着時間的流逝在SparkStreaming中爲每一個Key維護一份state狀態,
* 並且通過更新函數對該Key的狀態不斷更新,對於每個新的Batch而言,SparkStreaming會在使用updateStateByKey
* 的時候爲已經存在的Key進行State的狀態更新(對於每個新出現的Key,會同樣的執行State的更新函數操作),
* 但是如果通過更新函數對State更新後返回none,此時該Key對應的State會被刪除掉,需要特別說明的是,
* State可以是任意類型的數據結構,這就給我們的計算帶來了無限的想象空間
*
* 重點:如果要不斷的更新每個Key的State,就一定涉及到了狀態的保存和容錯,這個時候就需要開啓checkpoint機制和功能,需要
* 說明的是,checkpoint可以保存一切可以存儲在文件系統上的內容,例如程序未處理的數據以及已經擁有的狀態
*
* 補充說明:關於流式處理對歷史狀態進行保存和更新具有重大的實用意義,例如進行廣告點擊全面的動態評估
*
*/
public class UpdateStateByKeyDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
/**
* 第一步:配置SparkConf,
* 1、至少2條線程:因爲Spark Streaming應用程序在運行的時候至少有一條
* 線程用於不斷的循環接收數據,並且至少有一條線程用於處理接收的數據(否則的話無法有線程用
* 於處理數據,隨着時間的推移,內存和磁盤都會不堪重負)
* 2、對於集羣而言,每個Executor一般肯定不止一個Thread,那對於處理Spark Streaming
* 應用程序而言,每個Executor一般分配多少Core比較合適?根據經驗,5個左右的Core是最佳的
* (一個段子:分配爲奇數個Core表現最佳,例如3個、5個、7個Core等
*/
SparkConf conf = new SparkConf().setMaster("local[2]").setAppName("WordCountOnline");
/**
* 第二步:創建Spark StreamingContext:
* 1、這是SparkStreaming應用程序所有功能的起始點和程序調度的核心.
* SparkStreamingContext的構建可以基於SparkConf參數,也可以基於持久化的SparkStreamingContext的
* 內容來恢復過來(典型的場景是Driver崩潰後重新啓動,由於Spark Streaming具有連續7*24小時不間斷運行的特徵,
* 所以需要在Driver重新啓動後繼續上一次的狀態,此時的狀態恢復需要基於曾經的Checkpoint)
* 2、在一個Spark Streaming應用程序中可以創建若干個SparkStreamingContext對象,使用下一個SparkStreaming
* 之前需要把前面正在運行的SparkStreamingContext對象關閉掉,由此,我們得出一個重大啓發,SparkStreaming框架也只是
* Spark Core上的一個應用程序而已,只不過Spark Streaming框架想運行的話需要Spark工程師寫業務邏輯處理代碼
*/
JavaStreamingContext javassc = new JavaStreamingContext(conf, Durations.seconds(5));
//開啓checkpoint機制,把checkpoint中的數據放在這裏設置的目錄中,生產環境下一般放在hdfs中
javassc.checkpoint("/usr/Document/checkpoint");
/**
* 第三步:創建Spark Streaming輸入數據來源input Stream:
* 1、數據輸入來源可以基於File、HDFS、Flume、Kafka、Socket等
* 2、在這裏我們指定數據來源於網絡Socket端口,Spark Streaming連接上該端口並在運行的時候一直監聽該端口的數據
* (當然該端口服務首先必須存在),並且在後續會根據業務需要不斷有數據產生(當然對於Spark Streaming
* 應用程序的運行而言,有無數據其處理流程都是一樣的)
* 3、如果經常在每間隔5秒鐘沒有數據的話不斷啓動空的Job其實會造成調度資源的浪費,因爲並沒有數據需要發生計算;所以
* 實際的企業級生成環境的代碼在具體提交Job前會判斷是否有數據,如果沒有的話就不再提交Job;
*/
JavaReceiverInputDStream<String> lines = javassc.socketTextStream("Master", 9999);
/**
* 第四步:接下來就像對於RDD編程一樣基於DStream進行編程,原因是DStream是RDD產生的模板(或者說類),在Spark Streaming具體
* 發生計算前,其實質是把每個Batch的DStream的操作翻譯成爲對RDD的操作!
*
*/
JavaDStream<String> words = lines.flatMap(new FlatMapFunction<String, String>(){ //如果是Scala,由於SAM轉換,所以可以寫成val words = lines.flatMap(_.split(" "))
public Iterable<String> call(String line) throws Exception {
return Arrays.asList(line.split(" "));
}
});
/**
* 第4.2步:在單詞拆分的基礎上對每個單詞實例計數爲1,也就是word => (word, 1)
*/
JavaPairDStream<String, Integer> pairs = words.mapToPair(new PairFunction<String, String, Integer>() {
public Tuple2<String, Integer> call(String word) throws Exception {
// TODO Auto-generated method stub
return new Tuple2<String, Integer> (word, 1);
}
});
/**
* 在這裏通過updateStateByKey來以Batch Interval爲單位來對歷史狀態進行更新,這是功能上的一個非常大的改進,否則的話要完成同樣的目的
* 就可能需要把數據保存在redis/HDFS/HBase/數據庫中來不斷地完成同樣一個Key的state更新。
*
* 如果對性能有極爲苛刻的要求且數據量特別大的話可以考慮把數據放在分佈式的Redis或者Tachyon內存文件系統中
* 當然,從Spark 1.6.x開始可以嘗試使用mapWithState,Spark 2.x後,mapWithState應該非常穩定
*/
JavaPairDStream<String, Integer> wordsCount = pairs.updateStateByKey(
new Function2<List<Integer>, Optional<Integer>, Optional<Integer>>() {
public Optional<Integer> call(List<Integer> values, Optional<Integer> state)
throws Exception {
// TODO Auto-generated method stub
Integer updatedValue = 0;
if(state.isPresent())
updatedValue = state.get();
for(Integer value:values) {
updatedValue += value;
}
return Optional.of(updatedValue);
}
});
/**
* 此處的print並不會直接觸發Job的支持,因爲現在的一切都是在Spark Streaming框架的控制之下的,對於SparkStreaming
* 而言,具體是否觸發真正的Job運行是基於設置的Duration時間間隔的
*
* 注意,Spark Streaming應用程序要想執行具體的Job,對DStream就必須有ouptputstream操作
* outputstream有很多類型的函數觸發,例如print,saveAsTextFile,saveAsHadoopFiles等,
* 其中最爲重要的一個方法是foreachRDD,因爲Spark Streaming處理的結果一般會放在Redis、DB、DashBoard
* 等上面,所以foreachRDD主要就是用來完成這些功能的,而且可以隨意自定義具體數據到底放在哪裏。
*/
wordsCount.print();
/**
* Spark Streaming 執行引擎也就是Driver開始運行,Driver啓動的時候是位於一條新的線程中的,當然其內部有消息循環體,用於
* 接收應用程序本身或者Executor中的消息,
*/
javassc.start();
javassc.awaitTermination();
javassc.close();
}
}