SparkStreaming性能調優

一、數據接收並行度調優——創建更多的輸入DStream和Receiver

通過網絡接收數據時(比如Kafka,Flume),會將數據反序列化,並存儲在Spark的內存中。如果數據接收成爲系統的瓶頸,可以考慮並行化數據接收。每個輸入DStream都會在某個Worker的Executor上啓動一個Receiver,該Receiver接收一個數據流。因此可以通過創建多個輸入DStream,並配置它們接收數據源不同的分區數據,達到接收多個數據流的效果。

比如,一個接收兩個Kafka Topic的輸入DStream,可以拆分成兩個輸入DStream,每個分別接收一個topic的數據。這樣就會創建兩個Receiver,從而並行地接收數據,提高吞吐量。多個DStream可以使用union算子進行合併,從而形成一個DStream。後續的算子操作只需要針對合併之後的DSream即可。

代碼示例:

int numStreams = 5;

List<JavaPairDStream<String, String>> kafkaStreams = new ArrayList<JavaPairDStream<String, String>>(numStreams);

for (int i = 0; i < numStreams; i++) {

  kafkaStreams.add(KafkaUtils.createStream(...));

}

JavaPairDStream<String, String> unifiedDStream = streamingContext.union(kafkaStreams.get(0), kafkaStreams.subList(1, kafkaStreams.size()));

unifiedDStream.print();

 

二、數據接收並行度調優——調節block interval

數據接收並行度調優,除了創建更多輸入DStream和Receiver以外,還可以調節block interval。通過參數spark.streaming.blockInterval,可以設置block interval,默認是200ms。

對於大多數Receiver而言,在將接收到的數據保存到Spark的BlockManager之前,都會將數據切分成一個一個的block。每個batch的block數量,決定了該batch對應的RDD的partition的數量,以及針對該RDD執行transformation操作時創建的task數量。每個batch對應的task的數量可以大約估算出來,即batch interval / block interval。

比如,batch interval爲1s,block interval爲100ms,則會創建10個task。如果每個batch的task數量太少,即低於每臺機器的CPU Core,說明batch的task數量偏少,導致所有的CPU資源沒有被完全利用起來。此時應該爲batch增加block的數量,需要減小block interval。

但是,需要注意的是,推薦的block interval的最小值爲50ms,如果低於這個值,那麼大量的task的啓動時間可能會變成性能的一個開銷。

 

三、數據接收並行度調優——輸入流數據重分區

使用inputStream.repartition(<number of partitions>),將接收到的batch,分不到指定數量的機器上,然後進行後續操作。

 

四、任務啓動調度

如果每秒鐘啓動的task過多,比如每秒啓動50個,100個,那麼發送這些task去Worker節點上的Executor的性能開銷將會大大增加,可以使用下述操作減少這方面的性能開銷:

  1. Task序列化:使用Kryo序列化機制來序列化task,減小task的大小,從而減少發送這些task到各個Worker節點上的Executor的時間
  2. 執行模式:在Standalone模式下運行Spark,可以達到更少的task啓動時間

 

五、數據處理並行度調優

如果在計算的任何stage中使用的並行task的數量沒有足夠多,那麼集羣資源是無法被充分利用的。

舉例來說,對於分佈式的reduce操作,比如reduceByKey和reduceByKeyAndWindow,默認的並行task的數量是由spark.default.parallelism參數決定的。也可以在reduceByKey等操作中,傳入第二個參數,手動指定該操作的並行度,也可以調節全局的spark.default.parallelism參數。

 

六、數據序列化調優

數據序列化造成的系統開銷可以由序列化格式的優化來減小。在流式計算的場景下,由兩種類型的數據需要優化:

  1. 輸入數據:默認情況下,接收到的輸入數據,是存儲在Executor的內存中的,使用的持久化級別是StorageLevel.MEMORY_AND_SER_2。這意味着,數據被序列化爲字節流從而減小GC開銷,並且會複製以進行executor失敗的容錯。因此,數據首先會存儲在內存中,然後在內存不足時會溢寫到磁盤上,從而爲流式計算來保存所有需要的數據。這裏的序列化有明顯的性能開銷——Receiver必須反序列化從網絡接收到的數據,然後再使用Spark的序列化格式序列化數據。
  2. 流式計算操作生成的持久化RDD:流式計算操作生成的持久化RDD,可能會持久化到內存中。例如,窗口操作默認就會將數據持久化在內存中,因爲這些數據後面可能會在多個窗口中使用,並被處理多次。然而,不像Spark Core的默認持久化級別,StorageLevel.MEMORY_ONLY,流式計算操作生成的RDD的默認持久化級別是StorageLevel.MEMORY_ONLY_SER,默認就會減小GC開銷。

在上述的兩個場景中,使用Kyro序列化類庫可以減小CPU和內存的性能開銷。使用Kyro時,一定要考慮註冊自定義的類,並且禁用對應引用的tracking(spark.kyro.referenceTracking)。

在一些特殊的場景中,比如需要爲流式應用保持的數據總量並不是很多,也許可以將數據以非序列化的方式進行持久化,從而減少序列化和反序列化的CPOU開銷,而且又不會有太昂貴的GC開銷。舉例來說,如果設置的batch interval,並且沒有使用window操作,那麼可以通過顯式地設置持久化級別,來禁止持久化對數據進行序列化。這樣就可以減少用於序列化和反序列化的CPU性能開銷,並且不用承擔太多的GC開銷。

 

七、batch interval調優(最重要)

如果想讓一個運行在集羣上的Spark Streaming應用程序可以穩定,就必須儘可能快地處理接收到的數據。換句話說,batch應該在生成之後,儘可能快地處理掉。對於一個應用來說,可以通過觀察Spark UI上的batch處理時間來判斷batch interval的設置是否合適。batch處理的時間必須小於等於batch interval的值。

給予流式計算的本質,在固定集羣資源條件下,應用能保持的數據接收速率,batch interval的設置會有巨大的影響。例如,在WordCount例子中,對於一個特定的數據接收速率,應用業務可以保證每2秒打印一次單詞計數,而不是每500ms。因此batch interval需要設置,讓預期的數據接收速率可以在生產環境中保持住。

爲應用計算合適的batch大小,比較好的方法是先設置一個很保守的batch interval,比如5s~10s,以很慢的數據接收速率進行測試。要檢查應用是否跟得上這個數據速率,可以檢查每個batch的處理時間的延遲,如果處理時間與batch interval基本吻合,那麼應用就是穩定的。否則,如果batch調度的延遲持續增長,那麼久意味着應用無法跟得上這個速率,就是不穩定的。此時可以提升數據處理的速度,或者增加batch interval,以保證應用的穩定。

注意,由於臨時性的數據增長導致的暫時的延遲增長是合理的,只要延遲情況可以在短時間內回覆即可。

 

八、內存調優——內存資源

Spark Streaming應用需要的集羣內存資源,是由使用的transformation操作類型決定的。舉例來說,如果想要使用一個窗口長度爲10分鐘的window操作,那麼集羣就必須有足夠的內存來保存10分鐘內的數據。如果想要使用uodateStateByKey來維護許多key的state,那麼內存資源就必須足夠大。反過來說,如果想要做一個簡單的map-filter-store操作,那麼需要使用的內存就很少。

通常來說,通過Receiver接收到的數據,會使用StorageLevel.MEMPRY_AND_DISK_SER_2持久化級別來進行存儲,因此無法保存在內存中的數據就會溢寫到磁盤上。而溢寫到磁盤上,會降低應用的性能。因此,通常的建議是爲應用提供它需要的足夠的內存資源。

(建議在一個小規模的場景下測試內存的使用量,並進行評估)

 

九、內存調優——垃圾回收

內存調優的另一個方面是垃圾回收。對於流式應用來說,如果要獲得低延遲,肯定不能有因爲JVM垃圾回收導致的長時間延遲。有很多參數可以幫助降低內存使用和GC開銷:

  1. DStream的持久化:正如在“數據序列化調優”一節中提到的,輸入數據和某些操作產生的中間RDD,默認持久化時都會序列化爲字節。與非序列化的方式相比,這會降低內存和GC開銷。使用Kyro序列化機制可以進一步減少內存使用和GC開銷。進一步降低內存使用率,可以對數據進行壓縮,由spark.rdd.compress參數控制(默認false)
  2. 清理舊數據:默認情況下,所有輸入數據和通過DStream transformation操作生成的持久化RDD,會自動被清理。Spark Streaming會決定何時清理這些數據,取決於transformation操作類型。例如,在使用窗口長度爲10分鐘的window操作,Spark會保持10分鐘以內的數據,時間過了以後就會清理舊數據。但是在某些特定的場景下,比如Spark SQL和Spark Streaming整合使用時,在異步開啓的線程中,使用Spark SQL針對batch RDD進行執行查詢。那麼就需要讓Spark保存更長時間的數據,直到Spark SQL查詢結束。可以使用streamingContext.remember()方法來實現。
  3. CMS垃圾回收器:使用並行化的mark-sweep垃圾回收機制,被推薦使用,用來保持GC低開銷。雖然並行的GC會降低吞吐量,但是還是建議使用它,來減少batch的處理時間(降低處理過程中的gc開銷)。如果要使用,那麼要在driver端和executor端都開啓。在spark-submit中使用--driver-java-options設置;使用spark.executor.extraJavaOptions參數設置。-XX:+UseConcMarkSweppGC。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章