SparkStreaming注意要点

本文介绍了SparkStreaming的StreamingContext,强调了启动上下文后的限制,包括不能添加新计算、上下文不能重启以及只能激活一个StreamingContext。讨论了Input DStreams的类型,提醒在本地运行时避免使用'local'或'local [1]'。提到了updateStateByKey的使用及潜在问题,推荐使用实验性的mapWithState。此外,讲解了Output Operations,如print()和foreachRDD()的使用细节,以及Window Operations和transform操作的概念。最后,提醒了窗口间隔设置和资源优化的重要性。

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

http://spark.apache.org/docs/latest/streaming-programming-guide.html
Spark官网已经已经将用法与概念写的很详细了。这里写一点平时需要注意的要点。

Spark:是以批处理为主,用微批处理来处理流数据
Flink:以流处理为主,用流处理来处理批数据


StreamingContext

ssc ==> Source ==> DStream ==> Transformation ==> Ouput

StreamingContext是所有流功能的主要入口点

val conf = new SparkConf().setMaster("local[2]").setAppName("NetworkWordCount")
val ssc = new StreamingContext(conf, Seconds(1))

进入StreamingContext源码发现有一个主构造器与两个附属构造器

class StreamingContext private[streaming] (
    _sc: SparkContext,
    _cp: Checkpoint,
    _batchDur: Duration
  )

def this(sparkContext: SparkContext, batchDuration: Duration) = {
    this(sparkContext, null, batchDuration)
}


def this(conf: SparkConf, batchDuration: Duration) = {
    this(StreamingContext.createNewSparkContext(conf), null, batchDuration)
}

要记住的要点:

  • 一旦启动了上下文,就不能设置或添加新的流式计算。
  • 上下文停止后,无法重新启动。
  • 在JVM中只能同时激活一个StreamingContext。
  • StreamingContext上的stop()也会停止SparkContext。要仅停止StreamingContext,请将stop()called 的可选参数设置- stopSparkContext为false。
  • 只要在创建下一个StreamingContext之前停止前一个StreamingContext(不停止SparkContext),就可以重复使用SparkContext创建多个StreamingContexts。

Input DStreams and Receivers:
Input Dstreams分为两种:一种是有receiver的,一种是没有receiver的(textFileStream读取hdfs文件)。可以源码源码里面这个类的返回值是否返回一个ReceiverInputDStream判断。

要记住的要点

  • 在本地运行Spark Streaming程序时,请勿使用“local”或“local [1]”作为主URL。这两种方法都意味着只有一个线程将用于本地运行任务。如果您正在使用基于receiver的输入DStream(例如socket,Kafka,Flume等),那么将使用单个线程来运行接收器,而不留下用于处理接收数据的线程。因此,在本地运行时,始终使用“local [ n ]”作为主URL,其中n >要运行的接收器数量(有关如何设置主服务器的信息,请参阅Spark属性)。

  • 将逻辑扩展到在集群上运行时,分配给Spark Streaming应用程序的core数必须大于receiver数。否则系统将接收数据,但无法处理数据。


updateStateByKey:(不推荐使用)
该updateStateByKey操作允许您在使用新信息不断更新时保持任意状态。要使用它,您必须执行两个步骤。

  • 定义状态 - 状态可以是任意数据类型。
  • 定义状态更新功能 - 使用函数指定如何使用先前状态和输入流中的新值更新状态。
lines.flatMap(_.split(",")).map((_,1)).reduceByKey(_+_).updateStateByKey(updataFunction).print

def updataFunction(newValues:Seq[Int],preValues:Option[Int]):Option[Int]={
val newCount = newValues.sum
val oldCount =  preValues.getOrElse(0)
Some(newCount+oldCount)
}

注意:!!
若使用记得指定ssc.checkpoint(“url”),用来存储之前的处理结果。这样会导致产生很多小文件。
如何解决?=》写到外部DB里去,upsert一下;
或者在再处理结果里加个timpstamp字段,这样在某个点执行统计也一样可以出来。


socketTextStream的执行流程:
在这里插入图片描述

执行流程:
receiver就收数据源的数据,并根据存储级别进行备份;接着时间到了,receiver将block的块信息发给ssc,ssc提交给sc,sc生成job并将job进行分发


mapWithState: (@Experimental 实验性的)推荐使用,代替updateStateByKey

val result = lines.flatMap(_.split(",")).map((_,1)).reduceByKey(_+_)
def mappingFunc = (word:String,value:option[Int],state:State[Int])=>{
	val sum = value.getOrElse(0)+state.getOption().getOrElse(0)
	state.update(sum)
	(word,sum)
}
val state = result.mapWithState(StateSpec.function(mappingFunc))


Output Operations on DStreams:

print():打印前十条到控制台

!!!foreachRDD(func):通常用来通过网络传输将结果写到DB里面去,传入的func会在java端执行

object ForEachRDD {
    def main(args: Array[String]): Unit = {
      val conf=new SparkConf().setAppName("ForEacheRDD").setMaster("local[2]")
      val ssc=new StreamingContext(conf,Seconds(10))

      val DStream=ssc.socketTextStream("192.168.137.130",9998)
      //wc
      val result=DStream.flatMap(x=>x.split(",")).map(x=>(x,1)).reduceByKey(_+_)

      //把结果写入到mysql
      //foreachRDD把函数作用在每个rdd上
      result.foreachRDD(rdd=>{
        rdd.foreach(x=>{
          val con=getConnection()
          val word=x._1
          val count=x._2.toInt
          //sql
          val sql=s"insert into wc values('$word',$count)"
          //插入数据
          val pstmt=con.prepareStatement(sql)
          pstmt.executeUpdate()
          //关闭
          pstmt.close()
          con.close()
        })
      })

      ssc.start()
      ssc.awaitTermination()

  }
    def getConnection(): Connection={
      //加载驱动
      Class.forName("com.mysql.jdbc.Driver")
      //准备参数
      val url="jdbc:mysql://localhost:3306/test"
      val username="root"
      val password="123456"
      val con=DriverManager.getConnection(url,username,password)
      con
    }
}

以上代码中,如果我们把val con=getConnection()放在foreach外面,大家可以看看结果,会报下面的错误:

Caused by: java.io.NotSerializableException: com.mysql.jdbc.SingleByteCharsetConverter

这是因为foreachRDD(func) func运行在driver里

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

,connection这个对象不能跨机器传输。只要是跨网路的都需要序列化。

上面这个案例这样使用连接的话,这样每条记录写入DB时候都会开启连接,这样是非常费资源的。
可通过foreachRDD(rdd =>{rdd.foreachPartition(…)})和创建连接池来优化。

dstream.foreachRDD { rdd =>
  rdd.foreachPartition { partitionOfRecords =>
    // ConnectionPool is a static, lazily initialized pool of connections
    val connection = ConnectionPool.getConnection()
    partitionOfRecords.foreach(record => connection.send(record))
    ConnectionPool.returnConnection(connection)  // return to the pool for future reuse
  }
}


Window Operations:

Spark Streaming还提供了窗口化计算,这些计算允许您在滑动的数据窗口上应用变换。
如求近三分的数据,每隔一分钟测试一次。 在这里插入图片描述
如图所示,每当窗口在源DStream上滑动时,该窗口内的RDD被组合,每一次对三个time(自己设置的)进行计算,间隔两个time进行一次计算。所以要设置两个参数:
1.窗口长度 - 窗口的持续时间(图中的小框框)。
2.滑动间隔 - 执行窗口操作的时间间隔(图中每个框的间隔时间)。
如图所示就可能出现一个问题,设置的间隔太短就可能出现重复计算的可能,或者某些数据没有计算,这些也是很正常的。


transform:

val spamInfoRDD = ssc.sparkContext.newAPIHadoopRDD(...) // RDD containing spam information

val cleanedDStream = wordCounts.transform { rdd =>
  rdd.join(spamInfoRDD).filter(...) // join data stream with spam information to do data cleaning
  ...
}

用于一个RDD与DStream之间相互的操作,综合处理。
DStream <=>RDD
DStream.transform()里面就是拿到了一个rdd,这样就可以对需要的RDD进行关联操作。

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


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

    //黑名单
    val black=List("17")

    //读取数据生成rdd
    val blackRDD=ssc.sparkContext.parallelize(black).map(x=>(x,true))

    //输入数据
    //(name,age,gender) (zhangsan,3,2)
    val DStream=ssc.socketTextStream("192.168.137.130",8888)
    val output=DStream.map(x=>(x.split(",")(0),x)).transform(rdd=>{
          rdd.leftOuterJoin(blackRDD).
            filter(x=>x._2._2.getOrElse(false)!=true).map(x=>x._2._1)
    })

    //输出
    output.print()

    ssc.start()
    ssc.awaitTermination()
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值