SparkStreaming02

本文围绕SparkStreaming展开,介绍了一些算子,如性能更好的mapWithState、用于Dstream操作RDD的transform;阐述了action操作,重点是foreachRDD及其设计模式;还提及其他操作,包括DF和SQL操作、窗口计算、连接操作等,以及广播变量和应用监控等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、SparkStreaming一些算子

1、mapWithState VS updateStateByKey

性能比updateStateByKey更好。

2、transform

Dstream 操作RDD 使用该算子

(1)场景

数据一:日志信息

domain,time,traffic

ruozedata.com

ruozedata.com

baidu.com

数据二:已有的文件(黑名单)

domain

baidu

====》

(2)SparkCore实现

代码:

package com.HBinz.spark.streaming.day02

import org.apache.spark.{SparkConf, SparkContext}

import scala.collection.mutable.ListBuffer

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

    val sparkconf = new SparkConf().setAppName("leftJoinApp").setMaster("local[2]")
    val sc = new SparkContext(sparkconf)
    //数据一
    val input1 = new ListBuffer[(String,Long)]
    input1.append(("www.ruozedata.com",6666))
    input1.append(("www.ruozedata.com",7777))
    input1.append(("www.baidu.com",8888))
    //将可变List的input1转为RDD
    val data1 = sc.parallelize(input1)
    //数据二
    val input2 = new ListBuffer[(String,Boolean)]
    input2.append(("www.baidu.com",true))
    //将可变List的input2转为RDD
    val data2 = sc.parallelize(input2)
    //leftjoin:将左边表有的数据都取出来,并被右表有的数据标记true
    //leftOuterJoin方法传参的RDD是K-V格式,所以input2加上布尔类型做判断
    data1.leftOuterJoin(data2)
      //去除(www.baidu.com,(9999,Some(true)))
      .filter(x=>{
      x._2._2.getOrElse(false) != true
      })
      //拿到(www.ruozedata.com,xxxx)
      .map(x=>{
        (x._1,x._2._1)
      })
      .collect().foreach(println)
    sc.stop
  }
}

(2)SparkStreaming实现

代码:

package com.HBinz.spark.streaming.day02

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

import scala.collection.mutable.ListBuffer

object leftJoinStreamingApp {
  def main(args: Array[String]): Unit = {
    val sparkconf = new SparkConf().setMaster("local[2]").setAppName("leftJoinStreamingApp")
    val scc = new StreamingContext(sparkconf,Seconds(10))
    //数据二,RDD类型
    val input2 = new ListBuffer[(String,Boolean)]
    input2.append(("www.baidu.com",true))
    val data2 = scc.sparkContext.parallelize(input2)
    //数据一: nc -lk过来
    val lines = scc.socketTextStream("hadoop002",8888)
    //将流数据以逗号拆分,组合成(www.baidu.com,www.baidu.com,xxxx)
    val data1 = lines.map(x=>{
      (x.split(",")(0),x)
    })
    //通过tranform方法将流数据从Dstream(一系列RDD构成)转换为RDD,每一个数据一的RDDleftJoin每个转为RDD类型的数据二
    .transform(rdd=>{
      rdd.leftOuterJoin(data2)
    })
    //去除(www.baidu.com,((www.baidu.com,9999),true))
    .filter(x=>{
    x._2._2.getOrElse(false) != true
    }).map(x=>{
      (x._2._1)
    }).print()
    scc.start()
    scc.awaitTermination()
  }
}

输入:

www.ruozedata.com,6666

www.ruozedata.com,7777

www.ruozedata.com,8888

www.baidu.com,9999

输出:

场景:

www.yy.com

测试的时候会打test.yy.com的日志过来,需要你过滤。生产上通过广播变量的办法处理。

二、action(Output Operations on DStreams)

1、重点:foreachRDD()

场景:

主要用于输出到其他系统。

2、创建一个wc表,后面需要将RDD输出到wc表里

create database g3;

create table wc(word varchar(20),c int(10));

3、foreachRDD的设计模式

This is incorrect as this requires the connection object to be serialized and sent from the driver to the worker. Such connection objects are rarely transferable across machines. This error may manifest as serialization errors (connection object not serializable), initialization errors (connection object needs to be initialized at the workers), etc. The correct solution is to create the connection object at the worker.

它允许将数据发送到外部系统。但是,理解如何正确和高效地使用这个原语是很重要的。下面是一些要避免的常见错误。

通常,向外部系统写入数据需要创建一个connection(例如到远程服务器的TCP连接),并使用它将数据发送到远程系统。为此,开发人员可能会无意中尝试在Spark驱动程序上创建一个连接对象,然后尝试在Spark工作中使用它来将记录保存在RDDs中。例如,(在Scala中:

dstream.foreachRDD { rdd =>
  val connection = createNewConnection()  // executed at the driver
  rdd.foreach { record =>
    connection.send(record) // executed at the worker
  }
}

按照官网的方法代码:

package com.HBinz.spark.streaming.day02

import java.sql.{Driver, DriverManager}

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

object foreachRDDApp {
  def main(args: Array[String]): Unit = {
    val sparkconf = new SparkConf().setAppName("foreachRDDApp").setMaster("local[2]")
    val scc = new StreamingContext(sparkconf,Seconds(10))
    val lines = scc.socketTextStream("hadoop002",8888)
    val result = lines.flatMap(_.split(",")).map(x=>(x,1)).reduceByKey(_ + _)
    //TODO...result ===>  MySQL
    result.foreachRDD { rdd =>
      val connection = createNewConnection()  // 在每个RDD里面创建一个connection
      rdd.foreach { record =>
        //record的数据结构(word,次数)
        val word = record._1
        val count = record._2
        //将nc -ls过来的wc分别插入到Mysql的wc表里
        val sql = s"insert into wc(word,c) values ($word,$count)"
        //执行
        connection.createStatement().execute(sql)
      }
    }
    scc.start()
    scc.awaitTermination()
  }
  //实现createNewConectio方法
  def createNewConnection()={
    Class.forName("com.mysql.jdbc.Driver")
    DriverManager.getConnection("jdbc:mysql://hadoop002:3306/g3","root","123456")
  }
}

然而:这是不正确的,因为这需要序列化连接对象并将其从驱动程序发送到工作线程。此类连接对象很少跨计算机进行传输。这个错误可能表现为序列化错误(连接对象不可序列化)、初始化错误(连接对象需要在工作者处初始化)等。正确的解决方案是在工作端创建连接对象。

另外的做法:将connection放到executor里

//TODO...result ===>  MySQL
result.foreachRDD { rdd =>
  rdd.foreach { record =>
    //record的数据结构(word,次数)
    val connection = createNewConnection()  // 在每个RDD里面创建一个connection
    val word = record._1
    val count = record._2
    //将nc -ls过来的wc分别插入到Mysql的wc表里,word是String/varchar类型,所以这里要''
    val sql = s"insert into wc(word,c) values ('$word',$count)"
    //执行
    connection.createStatement().execute(sql)

但,还是会有个大问题,加入你的rdd数目很大,会造成不断请求connection,浪费性能。

最终:使用foreachPartition

 A better solution is to userdd.foreachPartition - create a single connection object and send all the records in a RDD partition using that connection.

代码:

package com.HBinz.spark.streaming.day02

import java.sql.{Driver, DriverManager}

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

object foreachRDDApp {
  def main(args: Array[String]): Unit = {
    val sparkconf = new SparkConf().setAppName("foreachRDDApp").setMaster("local[2]")
    val scc = new StreamingContext(sparkconf,Seconds(10))
    val lines = scc.socketTextStream("hadoop002",8888)
    val result = lines.flatMap(_.split(",")).map(x=>(x,1)).reduceByKey(_ + _)
    //TODO...result ===>  MySQL
//    result.foreachRDD { rdd =>
//      rdd.foreach { record =>
//        //record的数据结构(word,次数)
//        val connection = createNewConnection()  // 在每个RDD里面创建一个connection
//        val word = record._1
//        val count = record._2
//        //将nc -ls过来的wc分别插入到Mysql的wc表里,word是String/varchar类型,所以这里要''
//        val sql = s"insert into wc(word,c) values ('$word',$count)"
//        //执行
//        connection.createStatement().execute(sql)
//      }
//    }
      result.foreachRDD { rdd =>
      rdd.foreachPartition { partitionOfRecords =>
        val connection = createNewConnection()
        partitionOfRecords.foreach(record => {
                  val connection = createNewConnection()  // 在每个RDD里面创建一个connection
                  val word = record._1
                  val count = record._2
                  //将nc -ls过来的wc分别插入到Mysql的wc表里,word是String/varchar类型,所以这里要''
                  val sql = s"insert into wc(word,c) values ('$word',$count)"
                  //执行
                  connection.createStatement().execute(sql)
        })
        connection.close()
      }
    }
    scc.start()
    scc.awaitTermination()
  }
  //实现createNewConectio方法
  def createNewConnection()={
    Class.forName("com.mysql.jdbc.Driver")
    DriverManager.getConnection("jdbc:mysql://hadoop002:3306/g3","root","123456")
  }
}

测试:

(1)清理MySql数据

truncate table wc;

(2)提交

最优:连接池

(3)拓展:了解bonecp(http://www.jolbox.com/)

pom文件导入依赖

<dependency>
  <groupId>com.jolbox</groupId>
  <artifactId>bonecp</artifactId>
  <version>0.8.0.RELEASE</version>
</dependency>

1)BoneCPConfig

2)getConnection

总结:

1、foreachRDD

2、foreachPartition

3、Partition里面的进行操作

三、其他

1、DF and SQL操作

很容易去使用DF和SQL操作Streamingdata,你可以使用SparkContext创建SparkSession,从而使用SparkSession创建StreamingContext。它可以在driver失败之后重启。

就是:

将DStream转RDD,转DF,最后用SparkSQL处理。

/** DataFrame operations inside your streaming program */

val words: DStream[String] = ...

words.foreachRDD { rdd =>

  // Get the singleton instance of SparkSession
  val spark = SparkSession.builder.config(rdd.sparkContext.getConf).getOrCreate()
  import spark.implicits._

  // Convert RDD[String] to DataFrame
  val wordsDataFrame = rdd.toDF("word")

  // Create a temporary view
  wordsDataFrame.createOrReplaceTempView("words")

  // Do word count on DataFrame using SQL and print it
  val wordCountsDataFrame = 
    spark.sql("select word, count(*) as total from words group by word")
  wordCountsDataFrame.show()
}

2、Caching / Persistence

尽量不适用Caching,因为默认就保存在内存。

3、  Window Operations

Spark Streaming also provides windowed computations, which allow you to apply transformations over a sliding window of data. The following figure illustrates this sliding window.

Spark Streaming还提供了窗口计算,允许您通过滑动数据窗口应用申请转换

(1)重点:

窗口长度-窗口的持续时间(图3)。

滑动间隔——执行窗口操作的间隔(图中为2)。

这两个参数必须是源DStream的批处理间隔的倍数(图中的1)。

(2)案例:

每隔10s计算最近30s的数据

// Reduce last 30 seconds of data, every 10 seconds
val windowedWordCounts = pairs.reduceByKeyAndWindow((a:Int,b:Int) => (a + b), Seconds(30), Seconds(10))

(3)其他函数

(4)要checkpoint

(5)github源码

4、Join Operations

总结:

(1)DS可以直接joinDS,window也可以直接joinwindow

(2)DSjoinwindow要转为RDD再join

5、广播变量完成黑名单Accumulators, Broadcast Variables, and Checkpoints

有空看源码

https://github.com/apache/spark/blob/v2.3.2/examples/src/main/scala/org/apache/spark/examples/streaming/RecoverableNetworkWordCount.scala

6、Monitoring Applications

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

### Spark Streaming 使用指南与介绍 #### 什么是 Spark Streaming? Spark Streaming 是 Apache Spark 生态系统中的一个重要组件,它建立在 Spark Core 基础之上,用于处理实时数据流。官方将其定义为一种能够从多种数据源(如 Kafka、Flume、Kinesis 或 TCP 套接字)消费并处理数据的高吞吐量、容错性强的实时计算框架[^3]。 #### Spark Streaming 的工作原理 Spark Streaming 将输入的数据流划分为多个小批次(batches),每个批次被当作一个 RDD(弹性分布式数据集)。通过这种方式,Spark Streaming 能够利用 Spark 的核心功能来高效地处理这些小批量数据。其底层实现依赖于至少两条线程:一条负责接收数据(Receiver 线程),另一条则负责处理已接收的数据[^5]。 #### Spark Streaming 与传统 Spark 的主要区别 尽管两者都属于 Spark 生态的一部分,但它们的应用场景存在显著差异。Spark 更适合处理静态大数据集,而 Spark Streaming 则专注于实时流数据的处理。这种差异进一步影响到任务调度、执行方式以及容错机制等多个方面[^2]。 #### 如何使用 Spark Streaming 进行开发? 以下是基于 Scala 编写的简单示例程序,演示了如何使用 `updateStateByKey` 方法维护状态: ```scala import org.apache.spark.streaming.dstream.ReceiverInputDStream import org.apache.spark.streaming.{Seconds, StreamingContext} import org.apache.spark.{SparkConf, SparkContext} object Demo02_updateStateByKey { def main(args: Array[String]): Unit = { val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("WordCountWithState") val sc: SparkContext = new SparkContext(conf) sc.setLogLevel("ERROR") // 创建 StreamingContext 对象,并设置时间间隔为 5 秒 val ssc: StreamingContext = new StreamingContext(sc, Seconds(5)) // 设置 checkpoint 目录以支持有状态操作 ssc.checkpoint("./checkpointDir") // 接收来自 socket 数据源的数据 val dataStream: ReceiverInputDStream[String] = ssc.socketTextStream("localhost", 9999) // 执行单词计数逻辑,并更新状态 val statefulCounts = dataStream.flatMap(_.split("\\s+")) .map(word => (word, 1)) .updateStateByKey(updateFunc _) statefulCounts.print() ssc.start() ssc.awaitTermination() } /** * 自定义函数,用于更新每个 key 的状态值 */ def updateFunc(currentValues: Seq[Int], historyValue: Option[Int]): Option[Int] = { val currentSum = currentValues.sum val previousSum = historyValue.getOrElse(0) Some(currentSum + previousSum) } } ``` 此代码片段展示了如何配置 Spark Streaming 应用程序,包括创建上下文环境、指定检查点路径、读取网络套接字数据以及应用状态管理等功能[^4]。 #### 实验目标与学习要点 为了更好地掌握 Spark Streaming 技术,可以通过完成如下实验加深理解: - **对比分析**:研究 Spark Streaming 版本的 Word Count 和 MapReduce 版本之间的异同之处; - **熟悉流程**:深入了解 Spark Streaming 的整体运行机制及其内部架构设计; - **实践探索**:尝试构建自己的实时数据分析项目,验证理论知识的实际效果[^1]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值