最近在玩spark streaming, 感觉到了他的强大。 然后看 StreamingContext的源码去理解spark是怎么完成计算的。 大部分的源码比较容易看懂, 但是这个
reduceByKeyAndWindow(_+_, _-_, Duration, Duration)
还是花了不少时间。 主要还是由于对spark不熟悉造成的吧, 还好基本弄明白了。
总的来说SparkStreaming提供这个方法主要是出于效率考虑。 比如说我要每10秒计算一下前15秒的内容,(每个batch 5秒), 可以想象每十秒计算出来的结果和前一次计算的结果其实中间有5秒的时间值是重复的。
那么就是通过如下步骤
1. 存储上一个window的reduce值
2.计算出上一个window的begin 时间到 重复段的开始时间的reduce 值 =》 oldRDD
3.重复时间段的值结束时间到当前window的结束时间的值 =》 newRDD
4.重复时间段的值等于上一个window的值减去oldRDD
这样就不需要去计算每个batch的值, 只需加加减减就能得到新的reduce出来的值。
从代码上面来看, 入口为:
reduceByKeyAndWindow(_+_, _-_, Duration, Duration)
一步一步跟踪进去, 可以看到实际的业务类是在ReducedWindowedDStream 这个类里面:
代码理解就直接拿这个类来看了: 主要功能是在compute里面实现, 通过下面代码回调mergeValues 来计算最后的返回值
先计算oldRDD 和newRDD
//currentWindow 就是以当前时间回退一个window的时间再向前一个batch 到当前时间的窗口 代码里面有一个图很有用:
我们要计算的new rdd就是15秒-25秒期间的值, oldRDD就是0秒到10秒的值, previous window的值是1秒 - 15秒的值
然后最终结果是 重复区间(previous window的值 - oldRDD的值) =》 也就是中间重复部分, 再加上newRDD的值, 这样的话得到的结果就是10秒到25秒这个时间区间的值
得到newRDD和oldRDD后就要拿到previous windows的值: 如果第一次没有previous window那么建一个空RDD, 为最后计算结果时 arrayOfValues(0).isEmpty 铺垫
然后把所有的值放到一个数组里面 0是previouswindow, 1到oldRDD.size是oldrdd, oldRDD.size到newRDD.size是newrdd
将每个RDD的(K,V) 转变成(K, Iterator(V))的形式:
比如说有两个值(K,a) 和(K,b) 那么coGroup后就会成为(K, Iterator(a,b))这种形式
进行最后的计算:
首先判断RDD的value数量是不是正确 previous window因为已经计算过所以只有一组值
正确值为 1 (previous window value) + numOldValues (oldRDD 每个RDD的value) + numNewValues (newRDD 每个RDD的value)
接下来取出oldRDD的值和newRDD的值:
如果previous window是空的, 那么就直接计算newRDD的值(这也是为什么每次计算时候第一次打出来的值都比较少, 因为他只有newRDD部分没有重合部分, 也就是只有10秒的内容而不是15秒)
如果有previous window的值, 那么先存到tempValue, 如果有oldRDD那么减去oldRDD, 如果有newRDD (一般都有) 那么加上newRDD的值 这样就组成上图里面10到25秒区间的值了
最后如果有filter的function的话就filter一下:
这样就返回了新window内的值
reduceByKeyAndWindow(_+_, _-_, Duration, Duration)
还是花了不少时间。 主要还是由于对spark不熟悉造成的吧, 还好基本弄明白了。
总的来说SparkStreaming提供这个方法主要是出于效率考虑。 比如说我要每10秒计算一下前15秒的内容,(每个batch 5秒), 可以想象每十秒计算出来的结果和前一次计算的结果其实中间有5秒的时间值是重复的。
那么就是通过如下步骤
1. 存储上一个window的reduce值
2.计算出上一个window的begin 时间到 重复段的开始时间的reduce 值 =》 oldRDD
3.重复时间段的值结束时间到当前window的结束时间的值 =》 newRDD
4.重复时间段的值等于上一个window的值减去oldRDD
这样就不需要去计算每个batch的值, 只需加加减减就能得到新的reduce出来的值。
从代码上面来看, 入口为:
reduceByKeyAndWindow(_+_, _-_, Duration, Duration)
一步一步跟踪进去, 可以看到实际的业务类是在ReducedWindowedDStream 这个类里面:
代码理解就直接拿这个类来看了: 主要功能是在compute里面实现, 通过下面代码回调mergeValues 来计算最后的返回值
- val mergedValuesRDD = cogroupedRDD.asInstanceOf[RDD[(K, Array[Iterable[V]])]]
- .mapValues(mergeValues)
先计算oldRDD 和newRDD
//currentWindow 就是以当前时间回退一个window的时间再向前一个batch 到当前时间的窗口 代码里面有一个图很有用:
我们要计算的new rdd就是15秒-25秒期间的值, oldRDD就是0秒到10秒的值, previous window的值是1秒 - 15秒的值
然后最终结果是 重复区间(previous window的值 - oldRDD的值) =》 也就是中间重复部分, 再加上newRDD的值, 这样的话得到的结果就是10秒到25秒这个时间区间的值
- // 0秒 10秒 15秒 25秒
- // _____________________________
- // | previous window _________|___________________
- // |___________________| current window | --------------> Time
- // |_____________________________|
- //
- // |________ _________| |________ _________|
- // | |
- // V V
- // old RDDs new RDDs
- //
- val currentTime = validTime
- val currentWindow = new Interval(currentTime - windowDuration + parent.slideDuration,
- currentTime)
- val previousWindow = currentWindow - slideDuration
- val oldRDDs =
- reducedStream.slice(previousWindow.beginTime, currentWindow.beginTime - parent.slideDuration)
- logDebug("# old RDDs = " + oldRDDs.size)
- // Get the RDDs of the reduced values in "new time steps"
- val newRDDs =
- reducedStream.slice(previousWindow.endTime + parent.slideDuration, currentWindow.endTime)
- logDebug("# new RDDs = " + newRDDs.size)
得到newRDD和oldRDD后就要拿到previous windows的值: 如果第一次没有previous window那么建一个空RDD, 为最后计算结果时 arrayOfValues(0).isEmpty 铺垫
- val previousWindowRDD =
- getOrCompute(previousWindow.endTime).getOrElse(ssc.sc.makeRDD(Seq[(K, V)]()))
然后把所有的值放到一个数组里面 0是previouswindow, 1到oldRDD.size是oldrdd, oldRDD.size到newRDD.size是newrdd
- val allRDDs = new ArrayBuffer[RDD[(K, V)]]() += previousWindowRDD ++= oldRDDs ++= newRDDs
将每个RDD的(K,V) 转变成(K, Iterator(V))的形式:
比如说有两个值(K,a) 和(K,b) 那么coGroup后就会成为(K, Iterator(a,b))这种形式
- val cogroupedRDD = new CoGroupedRDD[K](allRDDs.toSeq.asInstanceOf[Seq[RDD[(K, _)]]],
- partitioner)
进行最后的计算:
- val mergeValues = (arrayOfValues: Array[Iterable[V]]) => {
- ...
- }
首先判断RDD的value数量是不是正确 previous window因为已经计算过所以只有一组值
正确值为 1 (previous window value) + numOldValues (oldRDD 每个RDD的value) + numNewValues (newRDD 每个RDD的value)
- if (arrayOfValues.size != 1 + numOldValues + numNewValues) {
- throw new Exception("Unexpected number of sequences of reduced values")
- }
接下来取出oldRDD的值和newRDD的值:
- val oldValues = (1 to numOldValues).map(i => arrayOfValues(i)).filter(!_.isEmpty).map(_.head)
- val newValues =
- (1 to numNewValues).map(i => arrayOfValues(numOldValues + i)).filter(!_.isEmpty).map(_.head)
如果previous window是空的, 那么就直接计算newRDD的值(这也是为什么每次计算时候第一次打出来的值都比较少, 因为他只有newRDD部分没有重合部分, 也就是只有10秒的内容而不是15秒)
- if (arrayOfValues(0).isEmpty) {
- // If previous window's reduce value does not exist, then at least new values should exist
- if (newValues.isEmpty) {
- throw new Exception("Neither previous window has value for key, nor new values found. " +
- "Are you sure your key class hashes consistently?")
- }
- // Reduce the new values
- newValues.reduce(reduceF) // return
- }
如果有previous window的值, 那么先存到tempValue, 如果有oldRDD那么减去oldRDD, 如果有newRDD (一般都有) 那么加上newRDD的值 这样就组成上图里面10到25秒区间的值了
- else {
- // Get the previous window's reduced value
- var tempValue = arrayOfValues(0).head
- // If old values exists, then inverse reduce then from previous value
- if (!oldValues.isEmpty) {
- tempValue = invReduceF(tempValue, oldValues.reduce(reduceF))
- }
- // If new values exists, then reduce them with previous value
- if (!newValues.isEmpty) {
- tempValue = reduceF(tempValue, newValues.reduce(reduceF))
- }
- tempValue // return
- }
最后如果有filter的function的话就filter一下:
- if (filterFunc.isDefined) {
- Some(mergedValuesRDD.filter(filterFunc.get))
- } else {
- Some(mergedValuesRDD)
- }
这样就返回了新window内的值