zoukankan      html  css  js  c++  java
  • 通过案例对SparkStreaming透彻理解三板斧之三

    本课将从二方面阐述:

    一、解密SparkStreaming Job架构和运行机制

    二、解密SparkStreaming容错架构和运行机制

      一切不能进行实时流处理的数据都将是无效的数据。在流处理时代,SparkStreaming有着强大吸引力,加上Spark的生态系统及各个子框架,SparkStreaming可以无缝的调用其兄弟框,如SQL,MLlib、Graphx等。掌握SparkStreaming架构及Job运行机制对精通SparkStreaming至关重要。通常的Spark应用程序是对RDD的Action操作触发了应用程序的Job的运行。而对于SparkStreaming,Job是怎么样运行的呢?在编写SparkStreaming程序的时候,可设置BatchDuration,SparkStreaming框架会自动启动Job并每隔BatchDuration时间会自动触发Job的调用。

     两个Job的概念

    1. 每隔BatchInterval时间片就会产生的一个个Job,这里的Job并不是Spark Core中的Job,它只是基于DStreamGraph而生成的RDD的DAG而已;从Java角度讲相当于Runnable接口的实现类,要想运行Job需要将Job提交给JobScheduler,在JobScheduler内部会通过线程池的方式创建运行Job的一个个线程,当找到一个空闲的线程后会将Job提交到集群运行(其实是在线程中基于RDD的Action触发真正的作业的运行)。为什么使用线程池呢?

      a.Job根据BatchInterval不断生成,为了减少线程创建而带来的效率提升我们需要使用线程池(这和在Executor中通过启动线程池的方式来执行Task有异曲同工之妙);

      b.如果Job的运行设置为FAIR公平调度的方式,这个时候也需要多线程的支持;

    2. 上面Job提交的Spark Job本身。单从这个时刻来看,此次的Job和Spark core中的Job没有任何的区别。

    下面通过运行代码示例来分析整个运行机制

    package com.dt.spark.sparkstreaming

    import org.apache.spark.SparkConf
    import org.apache.spark.streaming.{Seconds, StreamingContext}

    /**
    * 使用Scala开发集群运行的Spark 在线黑名单过滤程序
    * @author DT大数据梦工厂
    * 新浪微博:http://weibo.com/ilovepains/
    *
    * 背景描述:在广告点击计费系统中,我们在线过滤掉黑名单的点击,进而保护广告商的利益,只进行有效的广告点击计费
    * 或者在防刷评分(或者流量)系统,过滤掉无效的投票或者评分或者流量;
    * 实现技术:使用transform Api直接基于RDD编程,进行join操作
    */
    object OnlineForeachRDD2DB {
    def main(args: Array[String]){
    /**
    * 第1步:创建Spark的配置对象SparkConf,设置Spark程序的运行时的配置信息,
    * 例如说通过setMaster来设置程序要链接的Spark集群的Master的URL,如果设置
    * 为local,则代表Spark程序在本地运行,特别适合于机器配置条件非常差(例如
    * 只有1G的内存)的初学者 *
    */
    val conf = new SparkConf() //创建SparkConf对象
    conf.setAppName("OnlineForeachRDD") //设置应用程序的名称,在程序运行的监控界面可以看到名称
    // conf.setMaster("spark://Master:7077") //此时,程序在Spark集群
    conf.setMaster("local[6]")
    //设置batchDuration时间间隔来控制Job生成的频率并且创建Spark Streaming执行的入口
    val ssc = new StreamingContext(conf, Seconds(5))

    val lines = ssc.socketTextStream("Master", 9999)

    val words = lines.flatMap(_.split(" "))
    val wordCounts = words.map(x => (x, 1)).reduceByKey(_ + _)
    wordCounts.foreachRDD { rdd =>
    rdd.foreachPartition { partitionOfRecords => {
    // ConnectionPool is a static, lazily initialized pool of connections
    val connection = ConnectionPool.getConnection()
    partitionOfRecords.foreach(record => {
    val sql = "insert into streaming_itemcount(item,count) values('" + record._1 + "'," + record._2 + ")"
    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()

    }
    }

    package com.dt.spark.sparkstreaming;

    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.util.LinkedList;

    public class ConnectionPool {

    private static LinkedList<Connection> connectionQueue;

    static {
    try {
    Class.forName("com.mysql.jdbc.Driver");
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    }
    }

    public synchronized static Connection getConnection() {
    try {
    if(connectionQueue == null) {
    connectionQueue = new LinkedList<Connection>();
    for(int i = 0; i < 5; i++) {
    Connection conn = DriverManager.getConnection(
    "jdbc:mysql://Master:3306/sparkstreaming",
    "root",
    "778899..");
    connectionQueue.push(conn);
    }
    }
    } catch (Exception e) {
    e.printStackTrace();
    }
    return connectionQueue.poll();
    }

    public static void returnConnection(Connection conn) {
    connectionQueue.push(conn);
    }
    }

    将上述代码打成JAR包:

    启动SparkStreaming应用程序:

    具体执行细节是走的是SparkCore路线:

    使用foreachRDD的话将结果内容直接插入数据库中,不会进行打印结果输出:

    再看下整个运行过程:

    总调度器启动:Jobscheduler,主要是根据batchInterval或windows窗口移动进行作业划分,SparkStreaming不断接收流进来数据,不断生成Job,看下web控制台:

    随着时间的流失,不断生成job本身,job怎么生成?

    运行过程总结如下:

    1、在StreamingContext调用start方法的内部其实是会启动JobScheduler的Start方法,进行消息循环,在JobScheduler
      的start内部会构造JobGenerator和ReceiverTacker,并且调用JobGenerator和ReceiverTacker的start方法:
      JobGenerator启动后会不断的根据batchDuration生成一个个的Job
      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触发真正的作业的运行)。

    2、为什么使用线程池呢?
      作业不断生成,所以为了提升效率,我们需要线程池;这和在Executor中通过线程池执行Task有异曲同工之妙;
      有可能设置了Job的FAIR公平调度的方式,这个时候也需要多线程的支持;

     

    新浪微博:http://weibo.com/ilovepains

    微信公众号:DT_Spark

    博客:http://blog.sina.com.cn/ilovepains

    手机:18610086859

    QQ:1740415547

    邮箱:18610086859@vip.126.com

    Spark发行版笔记3

  • 相关阅读:
    第十三周进度条
    寻找水军
    第十二周进度条
    学习总结
    第十五周工作总结
    第十四周工作总结
    《梦断代码》阅读笔记03
    个人工作总结20
    个人工作总结19
    个人工作总结18
  • 原文地址:https://www.cnblogs.com/sparkbigdata/p/5468593.html
Copyright © 2011-2022 走看看