Spark Streaming应用

SparkStreaming 的用途(流式计算的应用场景)
1. 实时统计,累加(淘宝的大屏)
kafka + sparkstreaming(updateStatByKey,mapwithState)
2. 实时统计,最近一段时间指标
实时查看最近一个小时之内的用户点击量,各省或者重点城市(window窗口)

这里写图片描述

工作原理:

Spark Core
RDD
sc =new SparkContext(conf) :获取RDD

Spark SQL
DataFrame/DataSet
sqlContext=new SQLContext(sc) /HiveContext :获取DataFrame

Spark Streaming
DStream
ssc=new StreamingContext(sc,Seconds(1)); :

SparkStreaming的运行的原理
这里写图片描述

DStream分割的原理
这里写图片描述

Block interval:默认是200ms ,这个值是可以通过配置区修改的。
Batch inverval:这个值就是我们每次处理多长时间范围内的数据。是在new StreamingContext的时候就指定。
pom.xml的依赖

<dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-streaming_2.10</artifactId>
    <version>1.6.2</version>
</dependency>

安装nc工具。
NetCat是一个非常简单的Unix工具,可以读、写TCP或UDP网络连接(network connection)。它被设计成一个可靠的后端(back-end)工具,能被其它的程序程序或脚本直接地或容易地驱动。

Yum install -y nc 

Spark Streaming HDFS WordCount例子演示

import org.apache.spark.SparkConf
import org.apache.spark.streaming.StreamingContext
import org.apache.spark.streaming.Seconds

object HDFSWordcCount {
  def main(args: Array[String]): Unit = {
  /**
     * local[1]  中括号里面的数字都代表的是启动几个工作线程
     * 默认情况下是一个工作线程。那么做为sparkstreaming 我们至少要开启
     * 两个线程,因为其中一个线程用来接收数据,这样另外一个线程用来处理数据。
     */
    val conf = new SparkConf().setMaster("local[2]").setAppName("HDFSWordcCount")
      /**
     * Seconds  指的是每次数据数据的时间范围 (bacth interval)
     */
    val ssc = new StreamingContext(conf, Seconds(2));
    val fileDS = ssc.textFileStream("hdfs://hadoop1:9000/hdfs")
    val wordcountds = fileDS.flatMap { line => line.split("\t") }
      .map { word => (word, 1) }
      .reduceByKey(_ + _)
    wordcountds.print()
    ssc.start()
    ssc.awaitTermination();
  }
}

提交到spark集群进行运算的networdcount.sh文件

/usr/local/soft/spark/bin/spark-submit \
  --class com.xxxx.streaming.NetWordCount \
  --master spark://192.169.32.90:7077 \
  --driver-memory 500m \
  --executor-memory 500m \
  --total-executor-cores 2 \
  /usr/local/soft/streaming/networdcount.jar

这里写图片描述

修改:当新的数据添加后,在原始基础上继续计算


import org.apache.spark.streaming.StreamingContext
import org.apache.spark.SparkConf
import org.apache.spark.streaming.Seconds

object NetWordCountUpdateStateByKey {
  def main(args: Array[String]): Unit = {

    val conf = new SparkConf().setMaster("local[2]").setAppName("NetWordCountUpdateStateByKey")
    val ssc = new StreamingContext(conf, Seconds(2)); 
    val fileDS = ssc.socketTextStream("hadoop1", 9999)

    /**
     * 需要设置一个checkpoint的目录
     * 因为我们的计算结果有中间状态,这些中间状态需要存储
     */
    ssc.checkpoint(".")

    val wordDS = fileDS.flatMap { line => line.split("\t") }
      .map { word => (word, 1) }
    /**
     * updateFunc: (Seq[Int], Option[S]) => Option[S]
     * updateFunc 这是一个匿名函数
     *  (Seq[Int], Option[S]) 两个参数
     *  Option[S] 返回值
     *  首先我们考虑一个问题
     *  wordDS  做的bykey的计算,说明里面的内容是tuple类型,是键值对的形式,说白了是不是
     *  就是【K V】
     *  wordDS[K,V]
     *  (Seq[Int], Option[S])
     *  参数一:Seq[Int] Seq代表的是一个集合,int代表的是V的数据类型
     *              ---分组的操作,key相同的为一组 (hadoop,{1,1,1,1})
     *  参数二:Option[S] S代表的是中间状态State的数据类型,S对于我们的这个wordcount例子来讲,应该是
     *  int类型。中间状态存储的是单词出现的次数。 hadoop -> 4
     *
     *  Option[S] 返回值  应该跟中间状态一样吧。
     *  Option Some/None
     *
     */
    val wordcountDS = wordDS.updateStateByKey((values: Seq[Int], state: Option[Int]) => {
      val currentCount = values.sum; //获取此次本单词出现的次数
      val count = state.getOrElse(0); //获取上一次的结果 也就是中间状态
      Some(currentCount + count);
    })
    wordcountDS.print()
    //启动应用
    ssc.start()
    //等待任务结束
    ssc.awaitTermination()
  }
}

这里写图片描述
这里写图片描述

mapwithstate
与updateStateByKey方法相比,使用mapWithState方法能够得到6倍的低延迟同时维护的key状态数量要多10倍.
此外,更快的处理速度使得mapWithState 方法能够比updateStateByKey 方法管理多10倍的key(批处理间隔、集群大小、更新频率全部相同)。

import org.apache.spark.streaming.StreamingContext
import org.apache.spark.SparkConf
import org.apache.spark.streaming.Seconds
import org.apache.spark.streaming.StateSpec
import org.apache.spark.streaming.State

object MapWithStateDemo {
  def main(args: Array[String]): Unit = {
    /**
     * local[1]  中括号里面的数字都代表的是启动几个工作线程
     * 默认情况下是一个工作线程。那么做为sparkstreaming 我们至少要开启
     * 两个线程,因为其中一个线程用来接收数据,这样另外一个线程用来处理数据。
     */
    val conf = new SparkConf().setMaster("local[2]").setAppName("MapWithStateDemo")
    /**
     * Seconds  指的是每次数据数据的时间范围 (bacth interval)
     */
    val ssc = new StreamingContext(conf, Seconds(2));
    ssc.checkpoint(".")

    val fileDS = ssc.socketTextStream("hadoop1", 9999)
    val wordDstream = fileDS.flatMap { line => line.split("\t") }
      .map { word => (word, 1) }

    /**
     * word: String, one: Option[Int], state: State[Int]
     * 这个函数里面有三个参数
     * 第一个参数:word: String  代表的就是key
     * 第二个参数:one: Option[Int] 代表的就是value
     * 第三个参数:state: State[Int] 代表的就是状态(历史状态,也就是上次的结果)
     *
     */
    val mappingFunc = (word: String, one: Option[Int], state: State[Int]) => {
      val sum = one.getOrElse(0) + state.getOption.getOrElse(0)
      val output = (word, sum)
      state.update(sum)
      output
    }
     //初始化列表
    val initialRDD = ssc.sparkContext.parallelize(List(("hello", 1), ("world", 1)))

    val stateDstream = wordDstream.mapWithState(
      StateSpec.function(mappingFunc).initialState(initialRDD))

    stateDstream.print();
    //启动应用
    ssc.start()
    //等待任务结束
    ssc.awaitTermination()
  }
}

transform算子
transform操作,应用在DStream上时,可以用于执行任意的RDD到RDD的转换操作。它可以用于实现,DStream API中所没有提供的操作。比如说,DStream API中,并没有提供将一个DStream中的每个batch,与一个特定的RDD进行join的操作。但是我们自己就可以使用transform操作来实现该功能。

代码目的: 单词计数,但是单词里面,不需要统计 ? , ! 。也就是说在单词计数的时候过滤黑名单里面的标点符号。

import org.apache.spark.streaming.StreamingContext
import org.apache.spark.SparkConf
import org.apache.spark.streaming.Seconds

object TransformaDemo {
  def main(args: Array[String]): Unit = {

    val conf = new SparkConf().setMaster("local[2]").setAppName("TransformaDemo")
    val ssc = new StreamingContext(conf, Seconds(5));
    val fileDS = ssc.socketTextStream("192.168.32.110", 9999);
    val wordcountDS = fileDS.flatMap { line => line.split("\t") }
      .map { word => (word, 1) }

    val fillter = ssc.sparkContext.parallelize(List(",", "?", "!", ".")).map { param => (param, true) }

    val needwordDS = wordcountDS.transform(rdd => {
      val leftRDD = rdd.leftOuterJoin(fillter);
      //leftRDD String,(int,option[boolean]);
      val needword = leftRDD.filter(tuple => {
        val x = tuple._1;
        val y = tuple._2;
        if (y._2.isEmpty) {
          true;
        } else {
          false;
        }

      })
      needword.map(tuple => (tuple._1, 1))

    })

    val wcDS = needwordDS.reduceByKey(_ + _);
    wcDS.print();
    ssc.start();
    ssc.awaitTermination();
  }

}

这里写图片描述
这里写图片描述

window操作
每隔4秒实时统计前6秒的单词计数情况.(每2秒生成一个RDD)
这里写图片描述

import org.apache.spark.streaming.StreamingContext
import org.apache.spark.SparkConf
import org.apache.spark.streaming.Seconds

object reduceByKeyAndWindowDemo {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[2]").setAppName("TransformaDemo")
    val ssc = new StreamingContext(conf, Seconds(2));
    val fileDS = ssc.socketTextStream("192.168.32.110", 9999);
    val wordcountDS = fileDS.flatMap { line => line.split("\t") }
      .map { word => (word, 1) } //(_+_)
      .reduceByKeyAndWindow((x: Int, y: Int) => { x + y }, Seconds(6), Seconds(4))

    wordcountDS.print();
    ssc.start();
    ssc.awaitTermination();

  }
}

foreachRDD
通常在foreachRDD中,都会创建一个Connection,比如JDBC Connection,然后通过Connection将数据写入外部存储。

误区一:在RDD的foreach操作外部,创建Connection
这种方式是错误的,因为它会导致Connection对象被序列化后传输到每个Task中。而这种Connection对象,实际上一般是不支持序列化的,也就无法被传输。
这里写图片描述
误区二:在RDD的foreach操作内部,创建Connection
这种方式是可以的,但是效率低下。因为它会导致对于RDD中的每一条数据,都创建一个Connection对象。而通常来说,Connection的创建,是很消耗性能的。
这里写图片描述

使用方式一:
这里写图片描述
使用方式二:
这里写图片描述

使用数据库池实现实时单词计数:

<dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.6</version>
    </dependency>

首先创建好连接池


import java.sql.DriverManager
import java.sql.Connection

object MysqlPool {
  private val max = 8; //连接池的连接总数
  private val connectionNum = 10; //每次产生的连接数
  private var conNum = 0; //当前连接池已经产生的连接数

  import java.util
  private val pool = new util.LinkedList[Connection](); //连接池

  {

    Class.forName("com.mysql.jdbc.Driver")
  }
  /**
   * 释放连接
   */
  def releaseConn(conn: Connection): Unit = {
    pool.push(conn);
  }
  /**
   * 获取连接
   */
  def getJdbcCoon(): Connection = {
    //同步代码块
    AnyRef.synchronized({
      if (pool.isEmpty()) {
        for (i <- 1 to connectionNum) {
          val conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/xtwy", "root", "root");
          pool.push(conn);
          conNum + 1;
        }
      }

      pool.poll();
    })

  }
}

在Java开发特别是数据库开发中,经常会用到Class.forName( )这个方法。
通过查询Java Documentation我们会发现使用Class.forName( )静态方法的目的是为了动态加载类。
通常编码过程中,在加载完成后,一般还要调用Class下的newInstance( )静态方法来实例化对象以便操作。因此,单单使用Class.forName( )是动态加载类是没有用的,其最终目的是为了实例化对象。
有数据库开发经验朋友会发现,为什么在我们加载数据库驱动包的时候有的却没有调用newInstance( )方法呢?
即有的jdbc连接数据库的写法里是Class.forName(xxx.xx.xx);而有一 些:Class.forName(xxx.xx.xx).newInstance(),为什么会有这两种写法呢?
刚才提到,Class.forName(“”);的作用是要求JVM查找并加载指定的类,首先要明白,java里面任何class都要装载在虚拟机上才能运行,而静态代码是和class绑定的,class装载成功就表示执行了你的静态代码了,而且以后不会再走这段静态代码了。
而我们前面也说了,Class.forName(xxx.xx.xx)的作用就是要求JVM查找并加载指定的类,如果在类中有静态初始化器的话,JVM必然会执行该类的静态代码段。
而在JDBC规范中明确要求这个Driver类必须向DriverManager注册自己,即任何一个JDBC Driver的 Driver类的代码都必须类似如下:强调内容

public class MyJDBCDriver implements Driver {
    static {
        DriverManager.registerDriver(new MyJDBCDriver());
        }
}

既然在静态初始化器的中已经进行了注册,所以我们在使用JDBC时只需要Class.forName(XXX.XXX);就可以了。

开发代码把统计结果存进去。

import org.apache.spark.streaming.StreamingContext
import org.apache.spark.SparkConf
import org.apache.spark.streaming.Seconds

object ForEachRDDOperation {
  def main(args: Array[String]): Unit = {

    val conf = new SparkConf().setMaster("local[2]").setAppName("ForEachRDDOperation")

    val ssc = new StreamingContext(conf, Seconds(2));

    val fileDS = ssc.socketTextStream("192.168.32.110", 9999);
    val wordcountDS = fileDS.flatMap { line => line.split("\t") }
      .map { word => (word, 1) }
      .reduceByKey(_ + _)

    wordcountDS.foreachRDD(partitionOfRecords => {
      partitionOfRecords.foreachPartition(records => {
        val connect = MysqlPool.getJdbcCoon();
        while (records.hasNext) {
          val tuple = records.next();
          val sql = "insert into wordcount values( now(),'" + tuple._1 + "', " + tuple._2.toInt + ")";
          val statement = connect.createStatement();
          statement.executeUpdate(sql);
          print(sql);
        }
        MysqlPool.releaseConn(connect)

      })

    })

    ssc.start()

    ssc.awaitTermination()
  }
}

这里写图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值