基于案例贯通 Spark Streaming 流计算框架的运行源码

本博文主要内容包括:

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 获取上述资料)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值