尝试spark streaming的有状态转化: updateStateByKey和mapWithState

streaming wordCount示例

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

object StreamWordCount {

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

    val sparkConf = new SparkConf().setMaster("local[*]").setAppName("WordCount")
    val ssc = new StreamingContext(sparkConf, Seconds(5))
    val lineStreams = ssc.socketTextStream(localhost, 9999)

    val wordStreams = lineStreams.flatMap(_.split(" "))
    val wordAndOneStreams = wordStreams.map((_, 1))
    val wordAndCountStreams = wordAndOneStreams.reduceByKey(_+_)

    wordAndCountStreams.print()

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

在上面这种方式中,仅仅是对当前批次的word进行统计。

但是在实际的需求中,往往是需要一个累加的操作,需要跨批次的进行累加。

在streaming中,DStream的操作是分成无状态转化和有状态转化的。

无状态转化

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

具体的算子和spark core中基本没有差别,常见算子如下。

Transformation(转换) Meaning(含义)
map(func) 利用函数 func 处理原 DStream 的每个元素,返回一个新的 DStream。
flatMap(func) 与 map 相似,但是每个输入项可用被映射为 0 个或者多个输出项。。
filter(func) 返回一个新的 DStream,它仅仅包含原 DStream 中函数 func 返回值为 true 的项。
repartition(numPartitions) 通过创建更多或者更少的 partition 以改变这个 DStream 的并行级别(level of parallelism)。
union(otherStream) 返回一个新的 DStream,它包含源 DStream 和 otherDStream 的所有元素。
count() 通过 count 源 DStream 中每个 RDD 的元素数量,返回一个包含单元素(single-element)RDDs 的新 DStream。
reduce(func) 利用函数 func 聚集源 DStream 中每个 RDD 的元素,返回一个包含单元素(single-element)RDDs 的新 DStream。函数应该是相关联的,以使计算可以并行化。
countByValue() 在元素类型为 K 的 DStream上,返回一个(K,long)pair 的新的 DStream,每个 key 的值是在原 DStream 的每个 RDD 中的次数。
reduceByKey(func, [numTasks]) 当在一个由 (K,V) pairs 组成的 DStream 上调用这个算子时,返回一个新的,由 (K,V) pairs 组成的 DStream,每一个 key 的值均由给定的 reduce 函数聚合起来。注意:在默认情况下,这个算子利用了 Spark 默认的并发任务数去分组。你可以用 numTasks 参数设置不同的任务数。

有状态转化

UpdateStateByKey 操作

下面是官网描述的翻译:

updateStateByKey 操作允许您维护任意状态,同时不断更新新信息。你需要通过两步来使用它。

  1. 定义 state - state 可以是任何的数据类型。
  2. 定义 state update function(状态更新函数)- 使用函数指定如何使用先前状态来更新状态,并从输入流中指定新值。

在每个 batch 中,Spark 会使用状态更新函数为所有已有的 key 更新状态,不管在 batch 中是否含有新的数据。如果这个更新函数返回一个 none,这个 key-value pair 也会被消除。

使用updateStateByKey重新编写wordCount

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

object WorldCount {

  def main(args: Array[String]) {

    // 定义更新状态方法,参数values为当前批次单词频度,state为以往批次单词频度
    val updateFunc = (values: Seq[Int], state: Option[Int]) => {
      val currentCount = values.foldLeft(0)(_ + _)
      val previousCount = state.getOrElse(0)
      Some(currentCount + previousCount)
    }

    val conf = new SparkConf().setMaster("local[*]").setAppName("WordCount")
    val ssc = new StreamingContext(conf, Seconds(5))
    ssc.checkpoint("hdfs://hadoop:9000/chk")

    val lines = ssc.socketTextStream("hadoop", 9999)
    val words = lines.flatMap(_.split(" "))
    val pairs = words.map(word => (word, 1))

    val stateDstream = pairs.updateStateByKey[Int](updateFunc)
    stateDstream.print()

    //val wordCounts = pairs.reduceByKey(_ + _)
    //wordCounts.print()

    ssc.start()
    ssc.awaitTermination()
  }

}

mapWithState操作

理解起来和updateStateByKey差不多。
和map操作相比就是维护了历史状态


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

object WordCountWithState {

  //自定义mappingFunction,累加单词出现的次数并更新状态
  val mappingFunc = (word: String, count: Option[Int], state: State[Int]) => {
    val sum = count.getOrElse(0) + state.getOption.getOrElse(0)
    //必须进行的是历史状态的更新,然后要把累加的结果返回
    state.update(sum)
    (word, sum)
  }

  def main(args: Array[String]): Unit = {
    val sparkconf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
    val ssc = new StreamingContext(sparkconf,Seconds(5))
    ssc.checkpoint("data/chk")

    val scoketDStream = ssc.socketTextStream("localhost", 8888)
    val wordPair = scoketDStream.flatMap(_.split("\\s+"))
        .map(word => (word,1))

    wordPair.mapWithState(StateSpec.function(mappingFunc))
          .foreachRDD(rdd =>{
          	rdd.foreach(println(_))
          })


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

Window Operations

窗口类的算子也能实现带状态计算

常见如reduceByKeyAndWindow()

这里就不再写示例了。

总结

1.只要使用有状态转化的算子,必须设置checkpoint

2.upstatebykey会每批次将所有数据展示,例如历史数据是((“张三”, 1), (“罗翔”, 1)),

然后本批次输入为(“张三”)

打印结果是((“张三”, 2), (“罗翔”, 1))t

3.mapwithstate在每批次只展示更新的数据,例如历史数据是((“张三”, 1), (“罗翔”, 1)),

然后本批次输入为(“张三”)

打印结果是((“张三”, 2)

4.mapwithstate可以设置state的过期时间
5.想看mapwithstate源码梳理的可以看这个大佬的博客:https://blog.csdn.net/czmacd/article/details/54705988

你可能感兴趣的:(Spark)