sparkstreaming的状态计算-updateStateByKey源码
转发请注明原创地址:https://www.cnblogs.com/dongxiao-yang/p/11358781.html
本文基于spark源码版本为2.4.3
在流式计算中通常会有状态计算的需求,即当前计算结果不仅依赖于目前收到数据还需要之前结果进行合并计算的场景,由于sparkstreaming的mini-batch机制,必须将之前的状态结果存储在RDD中并在下一次batch计算时将其取出进行合并,这就是updateStateByKey方法的用处。
简单用例:
def main(args: Array[String]): Unit = {
val host = "localhost"
val port = "8001"
StreamingExamples.setStreamingLogLevels()
// Create the context with a 1 second batch size
val sparkConf = new SparkConf().setMaster("local[4]").setAppName("NetworkWordCount")
val ssc = new StreamingContext(sparkConf, Seconds(10))
ssc.checkpoint("/Users/dyang/Desktop/checkpoittmp")
val lines = ssc.socketTextStream(host, port.toInt, StorageLevel.MEMORY_AND_DISK_SER)
val words = lines.flatMap(_.split(" "))
val wordCounts: DStream[(String, Int)] = words.map(x => (x, 1))
//.reduceByKey(_ + _)
val totalCounts = wordCounts.updateStateByKey{(values:Seq[Int],state:Option[Int])=> Some(values.sum + state.getOrElse(0))}
totalCounts.print()
ssc.start()
ssc.awaitTermination()
}
上面例子展示了一个简单的wordcount版本的有状态统计,在updateStateByKey的作用下,应用会记住每个word之前count的总和并把下次到来的数据进行累加.
updateStateByKey拥有不同的参数封装版本,比较全的一个定义如下
/**
* Return a new "state" DStream where the state for each key is updated by applying
* the given function on the previous state of the key and the new values of each key.
* In every batch the updateFunc will be called for each state even if there are no new values.
* [[org.apache.spark.Partitioner]] is used to control the partitioning of each RDD.
* @param updateFunc State update function. Note, that this function may generate a different
* tuple with a different key than the input key. Therefore keys may be removed
* or added in this way. It is up to the developer to decide whether to
* remember the partitioner despite the key being changed.
* @param partitioner Partitioner for controlling the partitioning of each RDD in the new
* DStream
* @param rememberPartitioner Whether to remember the partitioner object in the generated RDDs.
* @tparam S State type
*/
def updateStateByKey[S: ClassTag](
updateFunc: (Iterator[(K, Seq[V], Option[S])]) => Iterator[(K, S)],
partitioner: Partitioner,
rememberPartitioner: Boolean): DStream[(K, S)] = ssc.withScope {
val cleanedFunc = ssc.sc.clean(updateFunc)
val newUpdateFunc = (_: Time, it: Iterator[(K, Seq[V], Option[S])]) => {
cleanedFunc(it)
}
new StateDStream(self, newUpdateFunc, partitioner, rememberPartitioner, None)
}
其中,参数里的updateFunc的是用户原本传入函数updateFunc: (Seq[V], Option[S]) => Option[S]的一次转化:
val cleanedUpdateF: (Seq[V], Option[S]) => Option[S] = 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)
最终updateStateByKey的结果是将一个PairDStreamFunctions转化成了一个StateDStream。对于所有的Dstream,compute(time)方法都是他们生成每个duration RDD的具体实现
override def compute(validTime: Time): Option[RDD[(K, S)]] = {
// Try to get the previous state RDD
getOrCompute(validTime - slideDuration) match {
case Some(prevStateRDD) => // If previous state RDD exists
// Try to get the parent RDD
parent.getOrCompute(validTime) match {
case Some(parentRDD) => // If parent RDD exists, then compute as usual
computeUsingPreviousRDD (validTime, parentRDD, prevStateRDD)
case None => // If parent RDD does not exist
// Re-apply the update function to the old state RDD
val updateFuncLocal = updateFunc
val finalFunc = (iterator: Iterator[(K, S)]) => {
val i = iterator.map(t => (t._1, Seq.empty[V], Option(t._2)))
updateFuncLocal(validTime, i)
}
val stateRDD = prevStateRDD.mapPartitions(finalFunc, preservePartitioning)
Some(stateRDD)
}
case None => // If previous session RDD does not exist (first input data)
// Try to get the parent RDD
parent.getOrCompute(validTime) match {
case Some(parentRDD) => // If parent RDD exists, then compute as usual
initialRDD match {
case None =>
// Define the function for the mapPartition operation on grouped RDD;
// first map the grouped tuple to tuples of required type,
// and then apply the update function
val updateFuncLocal = updateFunc
val finalFunc = (iterator: Iterator[(K, Iterable[V])]) => {
updateFuncLocal (validTime,
iterator.map (tuple => (tuple._1, tuple._2.toSeq, None)))
}
val groupedRDD = parentRDD.groupByKey(partitioner)
val sessionRDD = groupedRDD.mapPartitions(finalFunc, preservePartitioning)
// logDebug("Generating state RDD for time " + validTime + " (first)")
Some (sessionRDD)
case Some (initialStateRDD) =>
computeUsingPreviousRDD(validTime, parentRDD, initialStateRDD)
}
case None => // If parent RDD does not exist, then nothing to do!
// logDebug("Not generating state RDD (no previous state, no parent)")
None
}
}
}
这里需要解释一下parent的含义:parent,是本 DStream 上游依赖的 DStream,从上面updateStateByKey最后对StateDstream实例化代码可知,它将self也就是生成PairDStreamFunctions的Dstream本身传了进来构造了Dstream之间的DAG关系。
每个Dstream内部通过一个HashMap[Time, RDD[T]] ()来管理已经生成过的RDD列表, key 是一个 Time;这个 Time 是与用户指定的 batchDuration 对齐了的时间 —— 如每 15s 生成一个 batch 的话,那么这里的 key 的时间就是 08h:00m:00s,08h:00m:15s 这种,所以其实也就代表是第几个 batch。generatedRDD 的 value 就是 RDD 的实例,所以parent.getOrCompute(validTime)这个调用表示了获取经过上游Dstream的transfer操作后生成对应的RDD。
上述源码已经带了非常详细的注释,排除掉各种parentRDD/(prevStateRDD/initialRDD)不完整的边界情况之后,方法进入到了合并当前数据和历史状态的方法:computeUsingPreviousRDD
private [this] def computeUsingPreviousRDD(
batchTime: Time,
parentRDD: RDD[(K, V)],
prevStateRDD: RDD[(K, S)]) = {
// Define the function for the mapPartition operation on cogrouped RDD;
// first map the cogrouped tuple to tuples of required type,
// and then apply the update function
val updateFuncLocal = updateFunc
val finalFunc = (iterator: Iterator[(K, (Iterable[V], Iterable[S]))]) => {
val i = iterator.map { t =>
val itr = t._2._2.iterator
val headOption = if (itr.hasNext) Some(itr.next()) else None
(t._1, t._2._1.toSeq, headOption)
}
updateFuncLocal(batchTime, i)
}
val cogroupedRDD = parentRDD.cogroup(prevStateRDD, partitioner)
val stateRDD = cogroupedRDD.mapPartitions(finalFunc, preservePartitioning)
Some(stateRDD)
}
这个方法首先将当前数据parentRDD和prevStateRDD进行了cogroup运算,返回的数据类型位RDD[(K, (Iterable[V], Iterable[S]))],其中K是DStream的key的类型,value类型是当前数据的terable[V]和历史状态的Iterable[S])的二元Tuple,为了匹配这个参数类型spark将前面的updateFunc: (Iterator[(K, Seq[V], Option[S])])继续进行了封装
val finalFunc = (iterator: Iterator[(K, (Iterable[V], Iterable[S]))])
反过来看就是,最初形式为(K, (Iterable[V], Iterable[S]))的RDD数据经过一次封装变成了(Iterator[(K, Seq[V], Option[S])]格式再经过第二次封装变成了对用户自定义状态函数updateFunc: (Seq[V], Option[S]) => Option[S]的调用并返回RDD[(K, S)]格式的RDD。
注:
1 在spark源码中存在大量的隐式转换,比如updateStateByKey方法并不存在Dstream而是PairDStreamFunctions对象内,这是由于DStream的伴生对象中有一个隐式转换
implicit def toPairDStreamFunctions[K, V](stream: DStream[(K, V)])
(implicit kt: ClassTag[K], vt: ClassTag[V], ord: Ordering[K] = null):
PairDStreamFunctions[K, V] = {
new PairDStreamFunctions[K, V](stream)
}
所有符合DStream[(K, V)]类型的key-value都会通过这个隐式转换适配成PairDStreamFunctions对象
2 在使用状态算子的时候必须打开checkpoint功能,程序启动器就无法通过条件检查报错:
java.lang.IllegalArgumentException: requirement failed: The checkpoint directory has not been set. Please set it by StreamingContext.checkpoint()
参考文献:
sparkstreaming的状态计算-updateStateByKey源码的更多相关文章
- 基于HDFS的SparkStreaming案例实战和内幕源码解密
一:Spark集群开发环境准备 启动HDFS,如下图所示: 通过web端查看节点正常启动,如下图所示: 2.启动Spark集群,如下图所示: 通过web端查看集群启动正常,如下图所示: 3.启动sta ...
- Flume推送数据到SparkStreaming案例实战和内幕源码解密
本期内容: 1. Flume on HDFS案例回顾 2. Flume推送数据到Spark Streaming实战 3. 原理绘图剖析 1. Flume on HDFS案例回顾 上节课要求大家自己安装 ...
- Spark Streaming updateStateByKey案例实战和内幕源码解密
本节课程主要分二个部分: 一.Spark Streaming updateStateByKey案例实战二.Spark Streaming updateStateByKey源码解密 第一部分: upda ...
- Vue源码探究-状态初始化
Vue源码探究-状态初始化 Vue源码探究-源码文件组织 Vue源码探究-虚拟DOM的渲染 本篇代码位于vue/src/core/instance/state.js 继续随着核心类的初始化展开探索其他 ...
- 从Linux源码看TIME_WAIT状态的持续时间
从Linux源码看TIME_WAIT状态的持续时间 前言 笔者一直以为在Linux下TIME_WAIT状态的Socket持续状态是60s左右.线上实际却存在TIME_WAIT超过100s的Socket ...
- [源码解析] 深度学习流水线并行 GPipe(3) ----重计算
[源码解析] 深度学习流水线并行 GPipe(3) ----重计算 目录 [源码解析] 深度学习流水线并行 GPipe(3) ----重计算 0x00 摘要 0x01 概述 1.1 前文回顾 1.2 ...
- [源码解析] PyTorch 流水线并行实现 (4)--前向计算
[源码解析] PyTorch 流水线并行实现 (4)--前向计算 目录 [源码解析] PyTorch 流水线并行实现 (4)--前向计算 0x00 摘要 0x01 论文 1.1 引论 1.1.1 数据 ...
- [源码解析] PyTorch 流水线并行实现 (5)--计算依赖
[源码解析] PyTorch 流水线并行实现 (5)--计算依赖 目录 [源码解析] PyTorch 流水线并行实现 (5)--计算依赖 0x00 摘要 0x01 前文回顾 0x02 计算依赖 0x0 ...
- [源码解析] TensorFlow 分布式之 MirroredStrategy 分发计算
[源码解析] TensorFlow 分布式之 MirroredStrategy 分发计算 目录 [源码解析] TensorFlow 分布式之 MirroredStrategy 分发计算 0x1. 运行 ...
随机推荐
- Xadmin相关实现
一,保存搜索条件(Save search conditions) kept conditions(保存条件)的应用场景比较常见,在我们查询时,可以实现多条件的筛选查询,比如:在淘宝上,选择了其中的一个 ...
- cas多方式登录相关知识点的总结
知识点: cas多表单登录(在用户名,密码的基础上,增加短信验证码登录) 自定义认证策略 自定义字段添加为空校验的错误信息 Controller层接口的调用 一:场景 项目涉及到的业务是,在原cas用 ...
- c语言1-2019秋作业02
格式: 这个作业属于那个课程 C语言程序设计I 这个作业要求在哪里 http://edu.cnblogs.com/campus/zswxy/SE2019-3/homework/8688 我在这个课程的 ...
- Mysql中查询索引和创建索引
查询索引 show index from table_name 1.添加PRIMARY KEY(主键索引) ALTER TABLE `table_name` ADD PRIMARY KEY ( ` ...
- Hivesql中的正则
================================================================================================= 一般 ...
- Codeforces Round #587 (Div. 3) C. White Sheet
链接: https://codeforces.com/contest/1216/problem/C 题意: There is a white sheet of paper lying on a rec ...
- 转化对象为map方法
言语不多,直接上代码. private Map<String, Object> introspect(Object obj) throws Exception { Map<Strin ...
- 05 django组件:contenttype
1.django组件:contenttype 组件的作用:可以通过两个字段让表和N张表创建FK关系 1.专题课,学位课 如何关联 过期时间?? 方法1:分别创建 专题课--过期时间表 .学位课--过期 ...
- 斑马105SLPlus串口打印二维码
1.根据说明书调试硬件,校准介质还有色带(很重要),我自己搞了好几天才搞明白. 2.设置好参数,比如打印介质连续.非连续,热敏还是热转质 3.打印机上电后悔自动校准,校准成功后就可以直接通过串口打印, ...
- OFDM符号速率与子载波间隔的关系
一般采样速率fs等于符号速率Rb: 采样速率表示采样的快慢,现有N个采样点: 类似于路程(N)除以速度(fs)等于时间(Tb): 采样一个点所需时间Tb = N/fs: 即一个子载波持续时间为Tb = ...