updateStateByKey 操作允许您在使用新的信息持续更新时保持任意状态
1、定义状态 - 状态可以是任意数据类型。
2、定义状态更新功能 - 使用函数指定
例如
以 DStream 中的数据进行按 key 做 reduce 操作,然后对各个批次的数据进行累加
对于有状态操作,要不断的把当前和历史的时间切片的 RDD 累加计算,随着时间的流逝⌛,计算的数据规模会变得越来越大
updateStateBykey 函数有 6 种重载函数:
1、只传入一个更新函数,最简单的一种
updateFunc: (Seq[V], Option[S]) => Option[S]): DStream[(K, S)] = ssc.withScope {
updateStateByKey(updateFunc, defaultPartitioner())
}
例如,对于 wordcount,我们可以这样定义更新函数:
(values:Seq[Int],state:Option[Int])=>{
// 创建一个变量,用于记录单词出现次数
var newValue=state.getOrElse(0) //getOrElse 相当于 if....else.....
for(value <- values){
newValue +=value // 将单词出现次数累计相加
}
Option(newValue)
}
2、传入更新函数和分区数
def updateStateByKey[S: ClassTag](
updateFunc: (Seq[V], Option[S]) => Option[S],numPartitions: Int): DStream[(K, S)] = ssc.withScope {
updateStateByKey(updateFunc, defaultPartitioner(numPartitions))
}
3、传入更新函数和自定义分区
def updateStateByKey[S: ClassTag](
updateFunc: (Seq[V], Option[S]) => Option[S],partitioner: Partitioner): DStream[(K, S)] = ssc.withScope {
val cleanedUpdateF = sparkContext.clean(updateFunc)
val newUpdateFunc = (iterator: Iterator[(K, Seq[V], Option[S])]) => {
iterator.flatMap(t => cleanedUpdateF(t._2, t._3).map(s => (t._1, s)))
}
updateStateByKey(newUpdateFunc, partitioner, true)
}
4、传入完整的状态更新函数
前面的函数传入的都是不完整的更新函数,只是针对一个 key 的,他们在执行的时候也会生成一个完整的状态更新函数。
Iterator[(K, Seq[V], Option[S])]) => Iterator[(K, S)] 入参是一个迭代器,参数 1 是 key,参数 2 是这个 key 在这个 batch 中更新的值的集合,参数 3 是当前状态,最终得到 key-->newvalue
def updateStateByKey[S: ClassTag](
updateFunc: (Iterator[(K, Seq[V], Option[S])]) => Iterator[(K, S)],partitioner: Partitioner,rememberPartitioner: Boolean): DStream[(K, S)] = ssc.withScope {
new StateDStream(self, ssc.sc.clean(updateFunc), partitioner, rememberPartitioner, None)
}
例如,对于 wordcount:
val newUpdateFunc = (iterator: Iterator[(String, Seq[Int], Option[Int])]) => {
iterator.flatMap(t => function1(t._2, t._3).map(s => (t._1, s)))
}
5、加入初始状态
initialRDD: RDD[(K, S)] 初始状态集合
def updateStateByKey[S: ClassTag](
updateFunc: (Seq[V], Option[S]) => Option[S],
partitioner: Partitioner,
initialRDD: RDD[(K, S)]
): DStream[(K, S)] = ssc.withScope {
val cleanedUpdateF = sparkContext.clean(updateFunc)
val newUpdateFunc = (iterator: Iterator[(K, Seq[V], Option[S])]) => {
iterator.flatMap(t => cleanedUpdateF(t._2, t._3).map(s => (t._1, s)))
}
updateStateByKey(newUpdateFunc, partitioner, true, initialRDD)
}
6、是否记得当前的分区
def updateStateByKey[S: ClassTag](
updateFunc: (Iterator[(K, Seq[V], Option[S])]) => Iterator[(K, S)],
partitioner: Partitioner,
rememberPartitioner: Boolean,
initialRDD: RDD[(K, S)]
): DStream[(K, S)] = ssc.withScope {
new StateDStream(self, ssc.sc.clean(updateFunc), partitioner,
rememberPartitioner, Some(initialRDD))
}
def testUpdate={
val sc = SparkUtils.getSpark("test", "db01").sparkContext
val ssc = new StreamingContext(sc, Seconds(5))
ssc.checkpoint("hdfs://ns1/config/checkpoint")
val initialRDD = sc.parallelize(List(("hello", 1), ("world", 1)))
val lines = ssc.fileStream[LongWritable,Text,TextInputFormat]("hdfs://ns1/config/data/")
val words = lines.flatMap(x=>x._2.toString.split(","))
val wordDstream :DStream[(String, Int)]= words.map(x => (x, 1))
val result=wordDstream.reduceByKey(_ + _)
def function1(newValues: Seq[Int], runningCount: Option[Int]): Option[Int] = {
val newCount = newValues.sum + runningCount.getOrElse(0) // add the new values with the previous running count to get the new count
Some(newCount)
}
val newUpdateFunc = (iterator: Iterator[(String, Seq[Int], Option[Int])]) => {
iterator.flatMap(t => function1(t._2, t._3).map(s => (t._1, s)))
}
val stateDS=result.updateStateByKey(newUpdateFunc,new HashPartitioner (sc.defaultParallelism),true,initialRDD)
stateDS.print()
ssc.start()
ssc.awaitTermination()
}