How-to: Translate from MapReduce to Apache Spark http://blog.cloudera.com/blog/2014/09/how-to-translate-from-mapreduce-to-apache-spark/
这篇文章写得很好, 为从MR过渡到Spark提供了一个很好的指示.
文章wen'zh中间关于如何模仿MR的cleanup()方法,文章给出了解决方案, 不过说得略微不够详细,我想展开说一下.
文中主要是引用了spark mail list中的一份讨论(http://mail-archives.apache.org/mod_mbox/spark-user/201407.mbox/%3CCAPH-c_O9kQO6yJ4khXUVdO=+D4vj=JfG2tP9eqn5RPko=dRNAg@mail.gmail.com%3E)给出的解决方案:
Pleasebe careful with that, it will not work as expected. First, it would have to be:
rdd.mapPartitions { partition =>
// Some setup code here @1
val result =partition.map(yourfunction) @2
// Some cleanup code here @3
result
}
becausethe function passed in to mapPartitions() needs to return an
Iterator, and if you do it like this, then the cleanup code willrun
*before* the processing takes place because partition.map() isexecuted
lazily.
One example of what actually works is:
rdd.mapPartitions{ partition =>
if (!partition.isEmpty) {
// Some setup code here
partition.map(item => {
val output =yourfunction(item)
if (!partition.hasNext){
// Some cleanup code here
}
output
})
} else {
// return an empty Iterator of your return type
}
}
val result = partition.map(yourfunction)这句话到底做了什么呢?
partition是一个Iterator类的对象, Iterator::map()方法里面其实并没有执行任何的迭代,而只是生成了一个新的AbstractIterator对象, 这个对象的next()方法会调用用户传入的yourfunction方法.
也就是说, 其实partition.map()方法在创建AbstractIterator对象后, 就返回了,没有执行任何的数据处理相关的操作.
这样, 如果cleanup code放在partition.map()之后执行, 是完全没有起作用的.
下面讲解一下整个过程, 及各方法的调用地方:
首先定位到每个shullfe的拉取task:
func(context,rdd.iterator(partition,context))
RDD会实现自己的compute()方法.
MapPartitionsRDD的compute()方法:
ShuffledRDD的compute()方法:
可以看到, 这里就会从各个机器拉取shuffle数据, 然后创建iterator, 并返回;
这样, 我们通过发生的先后顺序,理一下程序的执行过程:
rdd.mapPartitions { partition=>
// Some setup code here @1 //在变换iterator时就会调用到;
val result =partition.map(yourfunction) //yourfunction是真正在不断从iterator取数据时进行调用的方法;
// Some cleanup code here @3//在变换iterator时就会调用到;
result
}
所以可以看到, 只有"yourfunction"这个字面量, 才会在真正从iterator中取数据时被调用到;
通过上面的分析, 就不难理解mail list里的那种写法了.
最后再总结一下:
lines.mapPartitions { valueIterator=>
if (valueIterator.isEmpty) {
Iterator[...]()
} else {
val dbConnection = ...
valueIterator.map { item =>
val transformedItem = ...
if (!valueIterator.hasNext) {
dbConnection.close()
}
transformedItem
}
}
}
另外说一点, 英文文档中说到:
"Evenso, it would only close the connection on the driver, not necessarily freeingresources allocated by serialized copies."
我觉得这个说的有误, 从上面分析可以看出, 其实RDD的compute()方法是在分布在各个executor中执行的,而不是在driver中执行的, 所以connection的open和close都是在executor中的task中执行的, 非driver中.