本博文主要内容包括:
1 在线动态计算分类分类最热门商品案例回顾与演示
2 基于案例贯穿Spark Streaming的运行源码
一:在线动态计算分类最热门商品案例代码:
import com.robinspark.utils.ConnectionPool
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来在线动态计算电商中不同类别中最热门的商品排名,例如手机这个类别下面最热门的三种手机,电视这个类别
* 下最热门的三种电视,该实例在实际生产环境下具有非常重大的意义;
*
* 实现技术: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]){
/**
* 第1步:创建Spark的配置对象SparkConf,设置Spark程序的运行时的配置信息,
*/
val conf = new SparkConf() //创建SparkConf对象
conf.setAppName("OnlineTheTop3ItemForEachCategory2DB") //设置应用程序的名称,在程序运行的监控界面可以看到名称
conf.setMaster("spark://Master:7077") //此时,程序在Spark集群
//conf.setMaster("local[2]")
//设置batchDuration时间间隔来控制Job生成的频率并且创建Spark Streaming执行的入口
val ssc = new StreamingContext(conf, Seconds(5))
ssc.checkpoint("/root/Documents/SparkApps/checkpoint")
val userClickLogsDStream = ssc.socketTextStream("Master", 9999)
//假设说这里的数据的格式:user item category,例如Rocky Samsung Android item + 商品进行计数 : map之后是个数组
val formattedUserClickLogsDStream = userClickLogsDStream.map(clickLog =>
(clickLog.split(" ")(2) + "_" + clickLog.split(" ")(1), 1))
// val categoryUserClickLogsDStream = formattedUserClickLogsDStream.reduceByKeyAndWindow((v1:Int, v2: Int) => v1 + v2,
// (v1:Int, v2: Int) => v1 - v2, Seconds(60), Seconds(20))
//每隔20秒更新一次,任意商品过去60秒钟被点击了多少次
val categoryUserClickLogsDStream = formattedUserClickLogsDStream.reduceByKeyAndWindow(_+_,
_-_, Seconds(60), Seconds(20))
//结合SparkSQL就得使用foreachRDD的方式
categoryUserClickLogsDStream.foreachRDD { rdd => {
if (rdd.isEmpty()) {
println("No data inputted!!!")
} else {
//这里RDD是窗口级别的,把许多RDD联合在一块,我们得到的是Tuple,过去60秒钟每种商品item被点击的次数。
val categoryItemRow = rdd.map(reducedItem => {
//key是类别+Item,value就是它总共出现多少次
val category = reducedItem._1.split("_")(0)
val item = reducedItem._1.split("_")(1)
val click_count = reducedItem._2
Row(category, item, click_count)
})
val structType = StructType(Array(
StructField("category", StringType, true),
StructField("item", StringType, true),
StructField("click_count", IntegerType, true)
))
//rdd也有依赖上下文
val hiveContext = new HiveContext(rdd.context)
val categoryItemDF = hiveContext.createDataFrame(categoryItemRow, structType)
categoryItemDF.registerTempTable("categoryItemTable")
//里面必须用到开窗函数row_number()分类PARTITION BY,子查询 subquery
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")
reseltDataFram.show()
val resultRowRDD = reseltDataFram.rdd
resultRowRDD.foreachPartition { partitionOfRecords => {
if (partitionOfRecords.isEmpty){
println("This RDD is not null but partition is null")
} else {
// ConnectionPool is a static, lazily initialized pool of connections
val connection = ConnectionPool.getConnection()
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 = connection.createStatement();
stmt.executeUpdate(sql);
})
ConnectionPool.returnConnection(connection) // return to the pool for future reuse
}
}
}
}
}
}
/**
* 在StreamingContext调用start方法的内部其实是会启动JobScheduler的Start方法,进行消息循环,在JobScheduler
* 的start内部会构造JobGenerator和ReceiverTacker,并且调用JobGenerator和ReceiverTacker的start方法:
* 1,JobGenerator启动后会不断的根据batchDuration生成一个个的Job
* 2,ReceiverTracker启动后首先在Spark Cluster中启动Receiver(其实是在Executor中先启动ReceiverSupervisor),在Receiver收到
* 数据后会通过ReceiverSupervisor存储到Executor并且把数据的Metadata信息发送给Driver中的ReceiverTracker,在ReceiverTracker
* 内部会通过ReceivedBlockTracker来管理接受到的元数据信息
* 每个BatchInterval会产生一个具体的Job,其实这里的Job不是Spark Core中所指的Job,它只是基于DStreamGraph而生成的RDD
* 的DAG而已,从Java角度讲,相当于Runnable接口实例,此时要想运行Job需要提交给JobScheduler,在JobScheduler中通过线程池的方式找到一个
* 单独的线程来提交Job到集群运行(其实是在线程中基于RDD的Action触发真正的作业的运行),为什么使用线程池呢?
* 1,作业不断生成,所以为了提升效率,我们需要线程池;这和在Executor中通过线程池执行Task有异曲同工之妙;
* 2,有可能设置了Job的FAIR公平调度的方式,这个时候也需要多线程的支持;
*
*/
ssc.start()
ssc.awaitTermination()
}
}
二、源码分析:
案例流程框架图 :
一:创建StreamingContext
1.StreamingContext源码如下:
2.从createNewSparkSparkContext可以看出Spark Streaming就是Spark Core上面的一个应用程序。
第二步:获取输入数据源
1.socketTextStream接收socket数据流。
2.创建SocketInputDStream实例。
3.通过SocketReceiver接收数据。
4.SocketReceiver中通过onstart方法调用receiver方法。
5.Receive方法通过接收来自相同网络端口的数据。
6.Receive接收到数据产生DStream,而DStream内部是以RDD的方式封装数据。
socketTextStream读取数据的调用过程如下:
三:根据自己的业务进行transformation操作:
第四步:调用start方法。
1.Start源码如下:
2.追踪JobScheduler的start方法源码如下:
JoScheduler的启动主要实现以下步骤:
1.创建eventLoop(消息循环体)的匿名类实现,主要是处理各类JobScheduler的事件。
3.JobScheduler负责动态作业调度的具体类。
JobScheduler是整个Job的调度器,本身用了一条线程循环去监听不同的Job启动,Job完成或失败等
4.其中receiverTracker的start方法源码如下:
ReceiverTracker的作用是: 处理数据接收,数据缓存,Block生成等工作。
ReceiverTracker是以发送Job的方式到集群中的Executor去启动receiver。
5.ReceiverTrackEndpoint用于接收来自Receiver的消息。
Receive接收消息:启动一个Job接收消息。
6.调用startReceiver方法在Executors上启动receiver.其中以封装函数startReceiverFunc的方式启动receiver.
7.在startReceiver方法内部会启动supervisor.
8.首先调用了onStart()方法,其实调用的是子类的onstart方法。
9.也就是ReceiverSupervisorImpl的onStart方法。
10.BlockGenerator的start方法启动了BlockIntervalTimer和BlockPushingThread.
11.回到上面,我们现在看ReceiverSupervisor.startReceiver方法的调用。
12.其中onReceiverStart方法在子类ReceiverSupervisorImpl的onReceiverStart,启用给ReciverTrackEndpoint发送registerReceiver消息。
13.此时,ReceiverTrackEndpoint接收到消息后会调用registerReceiver方法。
至此,ReceiverTrack的启动就完成了。下面就回到我们最初的代码。
1.JobScheduler的start方法:
2.启动JobGenerator,JobGenerator负责对DstreamGraph的初始化,DStream与RDD的转换,生成Job,提交执行等工作。
3.调用processEvent,以时间间隔发消息。
4.generateJobs中发time就是我们指点的batch Duractions
5.submitJobSet提交Job.
6.而我们提交的Job,是被JobHandle封装的。
总体流程如下图所示:
补充说明:
Spark运行的时候会启动作业,runDummySparkJob函数是为了确保Receiver不会集中在一个节点上。
博文内容源自DT大数据梦工厂Spark课程的总结笔记。相关课程内容视频可以参考:
百度网盘链接:http://pan.baidu.com/s/1slvODe1(如果链接失效或需要后续的更多资源,请联系QQ460507491或者微信号:DT1219477246 获取上述资料)。