通過以下案例切入對Spark Streaming流計算框架的運行源碼的解讀:
package com.dt.spark.cores import org.apache.spark.SparkConf import org.apache.spark.sql.Row import org.apache.spark.sql.hive.HiveContext import org.apache.spark.sql.types.{IntegerType, StringType, StructField, StructType} import org.apache.spark.streaming.{Seconds, StreamingContext} /** * 使用Spark Streaming+Spark SQL來在線動態計算電商中不同類別中最熱門的商品排名,例如手機這個類別下面最熱門的三種手機、電視這個類別 * 下最熱門的三種電視,該實例在實際生產環境下具有非常重大的意義; * @author DT大數據夢工廠 * 新浪微博:http://weibo.com/ilovepains/ * 實現技術:Spark Streaming+Spark SQL,之所以Spark Streaming能夠使用ML、sql、graphx等功能是因爲有foreachRDD和Transform * 等接口,這些接口中其實是基於RDD進行操作,所以以RDD爲基石,就可以直接使用Spark其它所有的功能,就像直接調用API一樣簡單。 * 假設說這裏的數據的格式:user item category,例如Rocky Samsung Android */ object OnlineTheTop3ItemForeachCategory2DB { def main(args: Array[String]) { val conf = new SparkConf(); conf.setAppName("OnlineTheTop3ItemForeachCategory2DB"); conf.setMaster("local[6]"); /** * 此處設置Batch Interval是在Spark Streaming中生成基本job的時間單位,窗口和滑動時間間隔 * 一定是改Batch Interval的整數倍 * 設置batchDuration時間間隔來控制Job生成的頻率並且創建Spark Streaming執行的入口 */ val ssc = new StreamingContext(conf, Seconds(5)); ssc.checkpoint("/root/Documents/SparkApps/") val userClickLogsDStream = ssc.socketTextStream("RockyLinux",9999) val formattedUserClickLogsDStream = userClickLogsDStream.map(clickLog => (clickLog.split(" ")(2) + "_" + clickLog.split(" ")(1),1)) val categorydUserClickLogsDStream = formattedUserClickLogsDStream.reduceByKeyAndWindow(_+_,_-_,Seconds(60),Seconds(20)) categorydUserClickLogsDStream.foreachRDD{ rdd => if(rdd.isEmpty()) { print("No rdd!!!") }else { val categoryItemRow = rdd.map(reducedItem => { val category = reducedItem._1.split("_")(0) val item = reducedItem._1.split("_")(1) val click_count = reducedItem._1.split("_")(2) Row(category, item, click_count) }) val structType = StructType(Array( StructField("category", StringType, true), StructField("item", StringType, true), StructField("click_count", IntegerType, true) )) val hiveContext = new HiveContext(rdd.context) val categoryItemDF = hiveContext.createDataFrame(categoryItemRow, structType) categoryItemDF.registerTempTable("categoryItemTable") val reseltDataFram = hiveContext.sql("SELECT category,item,click_count FROM (SELECT category,item,click_count,row_number()" + " OVER (PARTITION BY category ORDER BY click_count DESC) rank FROM categoryItemTable) subquery " + " WHERE rank <= 3") val resultRowRDD = reseltDataFrame.rdd resultRowRDD.foreachPartition { partitionOfRecords => { if (partitionOfRecords.isEmpty) {null print("This RDD is not null but Partition is null!") } else { val connnection = ConnectionPool.getConnnection() partitionOfRecords.foreach(record => { val sql = "insert into categoryTop3(category,item,client_count) values('" + record.getAs("category") + "','" +record.getAs("item") + "'," + record.getAs("click_count") + ")" val stmt = connnection.createStatement() stmt.executeUpdate(sql) }) ConnectionPool.returnConnection(connnection) } } } } } /** * 在StreamingContext調用start方法的內部其實是會啓動JobScheduler的start方法,進行消息循環,在JobScheduler * 的start內部會構造JobGenerator和ReceiverTracher,並且調用JobGenerator和ReceiverTacker的start方法: * 1.JobGenetor啓動後會不斷的根據batchDuratione生成一個個的Job * 2.ReceiverTracher啓動後首先在Spark Cluster中啓動Receiver(其實是在Executor中先啓動Receiver Supervisor) * 數據後會通過ReceiverSupervisor存儲到Executor並且把數據的Metadata信息發送給Driver中的ReceiverTracker, * 在ReceiverTracker內部會通過ReceiverBlockTracker來管理接受到的元數據信息 * 每個BatchInterval會產生一個具體的Job,其實這裏的Job不是Spark Core中所指的Job,它只是基於DStreamGraph而生成的Receiver * 的DAG而已,從Java角度講,相當於Runnable接口實例,此時要想運行Job需要提交給JobScheduler,在Jobscheduler中 * 通過線程單獨的線程來提交Job到集羣運行(其實是在線程中基於RDD的Action觸發正在的作業的運行),爲什麼使用線程池呢? * 1.作業不斷生成,所以爲了提升效率,我們需要線程池:這和在Executor中通過線程池執行Tack有異曲同工之妙; * 2.有可能設置了Job的FAIR公平調度的方式,這個時候也需要多線程的支持 */ ssc.start(); ssc.awaitTermination(); } }
查看以上代碼運行的WebUI
我們過濾他的源碼
總體流程概述
這就是他整個繼承結構
備註:
這是我的Spark版本定製班學習筆記
更多私密內容,請關注微信公衆號:DT_Spark
如果您對大數據Spark感興趣,可以免費聽由王家林老師每天晚上20:00開設的Spark永久免費公開課,地址YY房間號:68917580