【Spark】Spark Streaming编程实践(简单易懂 快速上手)

知识储备

1、数据处理的方式:

  • 流式数据处理(Streaming)
  • 批量数据处理(batch)

2、数据处理延迟的长短

  • 实时数据处理:毫秒级别
  • 离线数据处理:小时 or 天级别

3、Spark streaming 是准实时(秒、分钟)、微批次(时间)的数据处理框架

1 基本概念

1、Spark streaming用于流式数据的处理。

  • 支持的数据输入源很多,例如:kafka、Flume、Twitter、ZeroMQ和简单的TCP套接字等
  • 数据输入后可以使用Spark的高度抽象原语如:map、reduce、join、window等进行运算
  • 结果可以保存在很多地方,例如HDFS、数据库等

2、Spark Streaming使用离散化流作为抽象表示,叫做DStream,是随时间推移而收到的数据的序列。在内部,每个时间区间收到的数据都作为RDD存在,而DStream是由这些RDD组成的序列。

3、Spark Streaming程序基本步骤

  1. 通过创建输入DStream来定义输入源。
  2. 通过对DStream应用转换操作和输出操作来定义流计算。
  3. 使用streamingContext.start()来开始接收数据和处理流程。
  4. 通过streamingContext.awaitTermination()方法来等待处理结束(手动结束或因为错误而结束)。
  5. 可以通过streamingContext.stop()来手动结束流计算进程。

2 WordCount入门

object word {
  def main(args: Array[String]): Unit = {
    // 创建环境对象,传递两个参数,第一个参数表示环境配置,第二个参数表示批量处理的周期(采集周期)
    val conf = new SparkConf().setMaster("local").setAppName("SparkStreaming")
    val ssc = new StreamingContext(conf,Seconds(3))

    // 逻辑处理
    // 获取端口数据
    val lines = ssc.socketTextStream("brace", 9999)

    val words = lines.flatMap(_.split(" "))

    val wordToOne = words.map((_,1))

    val WordToCount = wordToOne.reduceByKey(_+_)

    WordToCount.print()

    // 关闭环境,由于SparkStreaming采集器是长期执行的任务,不能直接关闭
    // 如果main方法执行完毕,应用程序也会自动结束,所以不能让main方法执行完毕
//    ssc.stop()
    // 1、启动采集器
    ssc.start()
    // 2、等待采集器的关闭
    ssc.awaitTermination()
  }
}

在虚拟机输入:nc -lp 9999,传入数据

3 DStream的创建

3.1 文件数据源

3.1.1 用法和说明

文件数据流:能够读取所有HDFS API兼容的文件系统文件,通过fileStream方法进行读取,SparkStreaming会监控dataDirectory目录并不断处理移动进来的文件。

streamingContext.textFileStream(dataDirectory)

注意:

  • 文件需要有相同的数据格式
  • 文件进入目录的方式需要通过移动或重命名来实现
  • 一旦文件移动进目录,则不能修改

3.2 RDD队列

3.2.1 用法

测试过程中,通过ssc.queueStream(queueOfRDDs)创建DStream,每一个推送到这个队列的RDD都会作为一个DStream处理

3.2.2 案例

object word2 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("SparkStreaming")
    val ssc = new StreamingContext(conf,Seconds(3))

    // 创建RDD队列
    val rddQueue = new mutable.Queue[RDD[Int]]()

    val inputStream = ssc.queueStream(rddQueue,false)
    val mappedStream = inputStream.map((_,1))
    val reducedStream = mappedStream.reduceByKey(_+_)
    reducedStream.print()

    // 1、启动采集器
    ssc.start()

    for(i<- 1 to 5){
      rddQueue += ssc.sparkContext.makeRDD(1 to 300,10)
      Thread.sleep(2000)
    }
    // 2、等待采集器的关闭
    ssc.awaitTermination()
  }
}

3.3 自定义数据源

继承Receiver,实现onStartonStop方法来自定义数据采集

object word3 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("SparkStreaming")
    val ssc = new StreamingContext(conf,Seconds(3))

    val messageDS = ssc.receiverStream(new MyReceiver())
    messageDS.print()

    // 1、启动采集器
    ssc.start()

    // 2、等待采集器的关闭
    ssc.awaitTermination()
  }

  /**
   * 自定义数据采集器
   */
  class MyReceiver extends Receiver[String](StorageLevel.MEMORY_ONLY){
    private var flag = true

    override def onStart()={
      new Thread(new Runnable {
        override def run(): Unit = {
          while(flag){
            val msg = "采集的数据为:"+new Random().nextInt(10).toString
            store(msg)
            Thread.sleep(500)
          }
        }
      }).start()
    }

    override def onStop()={
      flag=false
    }
  }
}

3.4 Kafka数据源

3.4.1 版本

DirectAPI:由计算的Executor来主动消费Kafka的数据

3.4.2 Kafka 0-10 Direct

1、添加依赖

<dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-streaming-kafka-0-10_2.12</artifactId>
    <version>2.4.0</version>
</dependency>

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.10.1</version>
</dependency>

2、代码

object word4 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("SparkStreaming")
    val ssc = new StreamingContext(conf,Seconds(3))

    // 配置
    val kafkaParams = Map[String, Object](
      ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG -> "brace:9092",
      ConsumerConfig.GROUP_ID_CONFIG -> "aaa",
      "key.deserializer" -> "org.apache.kafka.common.serialization.StringDeserializer",
      "value.deserializer" -> "org.apache.kafka.common.serialization.StringDeserializer",
    )
    // 工具类
    val kafkaDtatDS = KafkaUtils.createDirectStream[String,String](
      ssc,
      LocationStrategies.PreferConsistent,
      ConsumerStrategies.Subscribe[String,String](Set("aaa"),kafkaParams)
    )
    
    kafkaDtatDS.map(_.value()).print()

    ssc.start()
    ssc.awaitTermination()
  }

}

3、创建topic

  • 查询
/usr/local/kafka/bin/kafka-topics.sh --bootstrap-server brace:9092 --list

or

/usr/local/kafka/bin/kafka-topics.sh --list --zookeeper brace:2181
  • 创建
/usr/local/kafka/bin/kafka-topics.sh --bootstrap-server brace:9092 --create --topic aaa  --partitions 3 --replication-factor 1  

or

/usr/local/kafka/bin/kafka-topics.sh --create --zookeeper brace:2181 --replication-factor 1 --partitions 3 --topic aaa
  • 使用
/usr/local/kafka/bin/kafka-console-producer.sh --broker-list brace:9092 --topic aaa
  • 消费
/usr/local/kafka/bin/kafka-console-consumer.sh --bootstrap-server brace:9092 --topic aaa --from-beginning

3.5 Flume

3.5.1

4 DStream转换

DStream上的操作与RDD相似,分为Transformations(转换)和Output Operations(输出)两种,此外转换操作中还有一些比较特殊的原语,如:updateStateByKey()、transform()

  • 无状态转换:每个批次的处理不依赖于之前批次的数据
  • 有状态转换:当前批次的处理需要使用之前批次的数据或者中间结果。有状态转换包括基于滑动窗口的转换和追踪状态变化的转换(updateStateByKey)。

4.1 无状态转化操作

无状态操作就是把简单的RDD转化操作应用的每个批次上,也就是转化DStream中的每一个RDD。

Transform

通过对源DStream的每个RDD应用RDD-to-RDD函数,创建一个新的DStream。支持在新的DStream中做任何RDD操作。

join

两个流之间的join将相同key的数据连接到一起

4.2 有状态转化操作

4.2.1 UpdateStateByKey

用于记录历史记录,有时候我们需要在DStream中跨批次维护状态(例如wordcount),针对这种情况该方法为我们提供了一个状态变量的访问,用于键值对形式的Dstream。

UpdateStateByKey的结果是一个新的DStream,其内部的RDD序列是由每个时间区间对应的(键,状态)对组成的

两步:

  • 定义状态,状态可以是任意的数据类型
  • 定义状态更新函数
例子
object word5 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("SparkStreaming")
    val ssc = new StreamingContext(conf,Seconds(3))

    ssc.checkpoint("cp")
    val datas = ssc.socketTextStream("brace",9999)
    val wordToOne = datas.map((_,1))
    // updateStateByKey根据key对数据的状态进行更新
    // 传递的参数中含有两个值
    // 第一个值表示相同key的value数据
    // 第二个值表示缓存区相同key的value数据
    val state = wordToOne.updateStateByKey (
      (seq:Seq[Int], buff:Option[Int])=>{
        val newCount = buff.getOrElse(0)+seq.sum
      Option(newCount)
      }
    )

    state.print()
    
    ssc.start()
    ssc.awaitTermination()
  }

}

4.2.2 Window Operations

可以设置窗口的大小和滑动窗口的间隔来动态的获取当前Steaming的允许状态。基于窗口的操作会在一个比streamingcontext的批次间隔更长的时间范围内,通过整合多个批次的结果,计算出整个窗口的结果。

应用场景

变化(交通流量等)

object word6 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("SparkStreaming")

    val ssc = new StreamingContext(conf,Seconds(3))

    val kafkaParams = Map[String, Object](
      ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG -> "brace:9092",
      ConsumerConfig.GROUP_ID_CONFIG -> "aaa",
      "key.deserializer" -> "org.apache.kafka.common.serialization.StringDeserializer",
      "value.deserializer" -> "org.apache.kafka.common.serialization.StringDeserializer",
    )

    val kafkaDataDS = KafkaUtils.createDirectStream[String,String](
      ssc,
      LocationStrategies.PreferConsistent,
      ConsumerStrategies.Subscribe[String,String](Set("aaa"),kafkaParams)
    )

    val value = kafkaDataDS.map(_.value())

    // 参数:间隔(大小:采集周期的整数倍)、滑动间隔(步长:采集周期的整数倍)
    value.window(Seconds(9),Seconds(3))

    value.print()

    ssc.start()
    ssc.awaitTermination()
  }
}

5 DStream输出

输出操作如下:

  • print()
  • saveAsTextFile(prefix,[suffix]):以txt文件形式存储DStream的内容
  • saveAsObjectFiles(prefix,[suffix]):以java对象序列化的方式将数据保存为SequenceFiles
  • saveAsHadoopFiles
  • foreachRDD()
import java.sql.{PreparedStatement, Connection, DriverManager}

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

    val conf = new SparkConf().setMaster("local[*]").setAppName("SparkStreaming")

    val ssc = new StreamingContext(conf,Seconds(3))

    val lines = ssc.socketTextStream("brace", 9998)

    val words = lines.flatMap(_.split(" "))

    val wordToOne = words.map((_,1))

    val WordToCount = wordToOne.reduceByKey(_+_)

    WordToCount.print()

    WordToCount.foreachRDD(rdd => {
      //内部函数
      def func(records: Iterator[(String,Int)]) {
        var conn: Connection = null
        var stmt: PreparedStatement = null
        
        try {
          val url = "jdbc:mysql://localhost:3306/spark"
          val user = "root"
          val password = "Hive@2020"
          conn = DriverManager.getConnection(url, user, password)
          
          records.foreach(p => {
            val sql = "insert into wordcount(word,count) values (?,?)"
            stmt = conn.prepareStatement(sql);
            stmt.setString(1, p._1.trim)
            stmt.setInt(2,p._2.toInt)
            stmt.executeUpdate()
          })
          
        } catch {
          case e: Exception => e.printStackTrace()
        } finally {
          if (stmt != null) {
            stmt.close()
          }
          if (conn != null) {
            conn.close()
          }
        }
      }
      
      val repartitionedRDD = rdd.repartition(3)
      repartitionedRDD.foreachPartition(func)
    })

    ssc.start()
    ssc.awaitTermination()
  }
}

6 优雅地关闭

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

快乐的冲浪码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值