通过以下案例切入对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

wKiom1cu2m7BCjBGAAFQI9a6VIc880.png


wKiom1cu2njTHDrVAAG56cFDMBA870.png

我们过滤他的源码

wKioL1cu21zQzw44AAJC-DWg-RM366.png

总体流程概述

wKiom1cu2ovwLOELAAIKn2kaqAk672.png


wKioL1cu23Gi4UG5AAIrU0XWen8959.png


wKiom1cu2p7iZnUhAAGelA29zGk244.png


wKiom1cu2qWDzWCNAAGpUrRwiNc855.png


wKioL1cu24mSCbttAAHwXF10sjU063.png


wKioL1cu25Cxu_TOAAH9yv6v9zk368.png

这就是他整个继承结构

wKiom1cu2r_CsLokAAKecbM5Cjo448.png


wKiom1cu2sniOuNLAAJnX3zrAhQ429.png


wKioL1cu262REguHAAIUV7HOeLM559.png


wKioL1cu27SSeDQgAAGJVBvi2Ak608.png


wKiom1cu2uORKACaAAIPQkPlnt0830.png


wKioL1cu28SRMDy0AAJQrlntI6A445.png


wKioL1cu282DqI7eAAI1t5NiKCQ797.png


wKiom1cu2v2gp1oGAAI46iqusX0829.png


wKioL1cu29yju4n0AAHBmXl8uLA006.png


wKiom1cu2wjBhzKrAAGLkOEJk8A308.png


wKiom1cu2w7jSyhoAAGQh01oOUU698.png


wKiom1cu2xbhB8ORAAH6TywKeSU953.png


wKiom1cu2x7R0vWpAAIGAIHNeLY341.png


wKioL1cu3ATRNqrQAAHRHPL_FW4475.png


wKioL1cu3A-RS9ldAAHLxS9G5mc388.png


wKiom1cu2z7AonHiAAIazPY8Ej4750.png


wKioL1cu3CXird8bAAIFCZtrep4438.png


wKioL1cu3DCiJ7_mAAIQMS7lnxM786.png


wKioL1cu3DvityRjAAJVwUxFQjY211.png


wKioL1cu3EKyJYwmAAI-NZhlzFA456.png

备注:

这是我的Spark版本定制班学习笔记

更多私密内容,请关注微信公众号:DT_Spark

如果您对大数据Spark感兴趣,可以免费听由王家林老师每天晚上20:00开设的Spark永久免费公开课,地址YY房间号:68917580