寫在前面
本文主要介紹Spark Streaming基本概念、kafka集成、Offset管理
本文主要介紹Spark Streaming基本概念、kafka集成、Offset管理
一、概述
Spark? Streaming顧名思義是spark的流式處理框架,是面向海量數據實現高吞吐量、高可用的分布式實時計算。關于spark的安裝可以參考Spark入門。Spark Streaming并非像Storm那樣是真正的流式計算,兩者的處理模型在根本上有很大不同:Storm每次處理一條消息,更多詳細信息可參考JStorm基本概念介紹;而spark streaming每次處理的是一個時間窗口的數據流,類似于在一個短暫的時間間隔里處理一批數據。
? ? spark streaming實時接收輸入數據流,并根據時間將數據流分成連續的多個batch,然后由Spark引擎一次處理一批數據,以批量生成最終結果流,工作流程圖:
二、Spak Streaming
? ? 2.1、Batch? Duration
????spark streaming的核心參數,設置流數據被分成多個batch的時間間隔,每個spark引擎處理的就是這個時間間隔內的數據。在Spark Streaming中,Job之間有可能存在依賴關系,所以后面的作業必須確保前面的作業執行完后才會被調度執行。如果批處理時間超過了batch duration,意味著數據處理速率跟不上數據接收速率,那么會導致后面正常的batch提交的作業無法按時執行,隨著時間的推移,越來越多的作業被延遲執行,最后導致整個Streaming作業被阻塞,所以需要設置一個合理的批處理間隔以確保作業能夠在這個批處理間隔內執行完成。
????application UI能詳細了解到每個batch的提交時間、數據處理時間、延遲執行時間以及處理的數據條目數。
? ? 雖然batchDuration的單位可以達到毫秒級別的,但是經驗告訴我們,如果這個值過小將會導致因頻繁提交作業從而給整個Streaming帶來負擔,所以請盡量不要將這個值設置為小于500ms。如果job執行的很快,而batchDuration設置的過長,依然會在上次提交作業間隔batchDuration后才提交下一個(數據流分隔機制決定的),這樣spark集群會有大空閑期,集群資源沒有被充分利用。spark streaming應用程序在首次啟動時同樣會間隔batchDuration才提交job(執行InputDStream.compute方法計算batch的RDD并提交作業)。
? ? 2.2、DStream
????表示一系列時間序列上連續的RDDs,每一個RDDs代表一定時間間隔內到達的數據,這樣就把連續的數據流拆成很多小的RDDs數據塊(RDDs數據塊內的數據是連續的數據)。可以通過實時數據創建DStream,也可以對現有的DStream進行transformation操作生成,例如map、window、reduceByKeyAndWindow等轉換操作。
????在spark streaming運行期間,每個DStream都會定期生成一個RDDs,具體的是compute(time) 方法,生成的RDDs代表一個批次內的數據,作為提交job的輸入元數據:
????在對DStream進行操作時,會被Spark Streaming引擎轉化成對底層 RDD操作。
????foreachRDD:是一個轉換輸出操作符,它返回的不是RDD里的一行數據, 而是輸出DStream后面的RDDs,表示一個批次中的一批數據,一個批次,只有一個RDDs。對于DirectKafkaInputDStream流返回的是KafkaRDD,需要注意的是該操作在運行spark streaming應用程序的driver進程里執行。
? ? 2.3、InputDStream
InputDStream繼承自DStream,是所有輸入流的基類,代表從源接收到的原始數據流DStreams,每一個InputDStream關聯到單個Receiver對象,從源數據接收數據并存儲到spark內存,等待處理。每一個InputDStream接收到的是單個數據流數據。InputDStream在driver節點上從新數據生成RDDs;如果為了實現input stream在work節點上運行recvicer接收外部數據,需要繼承ReceiverInputDStream類。InputDStream的start()、stop()方法,分別用于Spark Streaming系統啟動和停止接收數據時調用。
三、kafka集成
? ? 3.1、DirectKafkaInputDStream
????DirectKafkaInputDStream繼承InputDStream,創建方法:
????Subscribe有三個參數:topic列表、consumer配置項、topic+partition起始offset,其中fromOffsets是可選的。
????driver會根據kafkaParams創建KafkaConsumer,用于Spark Streaming確定batch內的kafka數據(offset)范圍。
? ? 3.2、KafkaRDD
????Spark Streaming每隔一個時間間隔會調用InputDStream.compute方法創建KafkaRDD(在driver上執行),表示這個batch里接收到的kafka數據,然后在提交作業時作為stream job的輸入。KafkaRDD extends RDD,實現了compute方法,用于計數一個分區里的數據、返回KafkaRDDIterator迭代器,迭代器內部next方法調用consumer.get,從kafka拉取數據.? ?
????job運行時調用KafkaRDD.compute方法從kafka讀取數據,也就是實際get操作發生在task中。
????KafkaRDD是一個包括topic、partition、fromeOffset、untilOffset等的數據結構;ConsumerRecord是kafka client的api。
? ? 3.3、offset初始化
? ? Spark Streaming在啟動時先調用Subscribe.onStart方法,初始化KafkaConsumer,這個Consumer對象是在driver中用于獲取offset。如果fromOffsets不為空,kafkaConsumer就seek到指定的offset,然后再調用positon獲取offset.
????如果fromOffsets是空,即沒有seek,當用consumer.position方法時,返回的offset取決于auto.offset.reset配置:earliest,獲取partition最早的offset;latest獲取partition最近的offset。
? ? 3.4、latestOffset
? ? spark Streaming的內部邏輯,上一個job的untilOffset成為下一個job的fromOffset。latestOffset函數計算untilOffset,核心計算思想是先consumer.seekToEnd,然后position函數就可以取得當前最后offset:
?四、offset管理
? ? enable.auto.commit參數必須設置false,因為在自動commit的情況下,可能在一個batch內的數據還沒有處理完、或者處理失敗,但offset就自動提交了,就會導致數據丟失。下面是在zk中管理offset的思路,zk簡單方便而且保證了可用性。
????在spark Streaming作業開始時,readOffsets函數用于從zk讀取上次應用保存的最后處理的消息偏移量,有以下兩種不同處理場景:
????1、Spark Streaming應用程序首次運行時,從zk read不到數據,那么就創建一個KafkaConsumer對象,用consumer.position的方式獲取offset,這時獲取到的offset取決于auto.offset.reset參數的設置
2、如果是重啟Spark Streaming應用程序,那可以直接從zk讀取到應用上次保存的offset
????在完成kafka DStream處理后,調用persistOffsets方法持久化保存分區的偏移量
整體過程偽代碼:
? ? 五、反壓
? ? 如果在一個batch內收到的消息比較多,這就需要為executor分配更多內存,可能會導致其他spark streaming應用程序資源分配不足,甚至有OOM的風險。特別是第一次啟動應用程序,從earliest offset消費數據時,kafka保留的歷史消息越多,數據處理時間也就越長。反壓可以限制每個batch接收到的消息量,降低數據傾斜的風險,開啟反壓:
SparkConf.set("spark.streaming.backpressure.enabled", "true")
設置每個kafka partition讀取消息的最大速率:
SparkConf.set("spark.streaming.kafka.maxRatePerPartition", "spark.streaming.kafka.maxRatePerPartition")
這個值要結合spark Streaming處理消息的速率和batchDuration,盡量保證讀取的每個partition數據在batchDuration時間內處理完,這個參數需要不斷調整,以做到盡可能高的吞吐量.
本文首發于公眾號:data之道