我正在寻找一种将RDD分成两个或更多RDD的方法。我见过的最接近的是 Scala Spark: Split collection into several RDD? ,它仍然只是一个RDD。
如果您熟悉SAS,请执行以下操作:
data work.split1, work.split2;
set work.preSplit;
if (condition1)
output work.split1
else if (condition2)
output work.split2
run;
导致两个不同的数据集。必须立即坚持以获得我想要的结果......
答案 0 :(得分:57)
不可能从单个转换*中产生多个RDD。如果要拆分RDD,则必须为每个拆分条件应用filter
。例如:
def even(x): return x % 2 == 0
def odd(x): return not even(x)
rdd = sc.parallelize(range(20))
rdd_odd, rdd_even = (rdd.filter(f) for f in (odd, even))
如果你只有一个二元条件并且计算成本很高,你可能更喜欢这样的东西:
kv_rdd = rdd.map(lambda x: (x, odd(x)))
kv_rdd.cache()
rdd_odd = kv_rdd.filter(lambda kv: kv[1]).keys()
rdd_even = kv_rdd.filter(lambda kv: not kv[1]).keys()
这意味着只有一个谓词计算,但需要额外传递所有数据。
重要的是要注意,只要输入RDD被正确缓存并且没有关于数据分布的额外假设,当涉及重复过滤器和具有嵌套if-else的for循环之间的时间复杂度时,没有显着差异。
对于N个元素和M条件,您必须执行的操作数明显与N倍M成比例。在for循环的情况下,它应该更接近(N + MN)/ 2并且重复过滤器恰好是NM但是在一天结束时,它只不过是O(NM)。您可以通过Jason Lenderman查看我的讨论**,了解一些利弊。
在非常高的层次上你应该考虑两件事:
Spark转换是懒惰的,直到您执行RDD未实现的操作
为什么重要?回到我的例子:
rdd_odd, rdd_even = (rdd.filter(f) for f in (odd, even))
如果稍后我决定只需要rdd_odd
,那么就没有理由实现rdd_even
。
如果您查看SAS示例来计算work.split2
,则需要实现输入数据和work.split1
。
RDD提供声明性API。当您使用filter
或map
时,完全取决于Spark引擎如何执行此操作。只要传递给转换的函数是免费的副作用,它就会创建多种优化整个管道的可能性。
在一天结束时,这个案例并不足以证明自己的转型是正确的。
这个带有过滤器模式的地图实际上是在核心Spark中使用的。请参阅我对How does Sparks RDD.randomSplit actually split the RDD的回答和randomSplit
方法的relevant part。
如果唯一的目标是实现对输入的拆分,则可以对partitionBy
使用DataFrameWriter
子句,其文本输出格式为:
def makePairs(row: T): (String, String) = ???
data
.map(makePairs).toDF("key", "value")
.write.partitionBy($"key").format("text").save(...)
* Spark中只有3种基本类型的转换:
其中T,U,W可以是原子类型,也可以是products /元组(K,V)。必须使用上述的某些组合来表达任何其他操作。您可以查看the original RDD paper了解详情。
** http://chat.stackoverflow.com/rooms/91928/discussion-between-zero323-and-jason-lenderman
答案 1 :(得分:5)
正如上面提到的其他海报,没有单一的原生RDD变换可以分割RDD,但这里有一些"多路复用"可以有效模拟各种各样的"分裂"在RDD上,没有多次阅读:
http://silex.freevariable.com/latest/api/#com.redhat.et.silex.rdd.multiplex.MuxRDDFunctions
一些特定于随机拆分的方法:
http://silex.freevariable.com/latest/api/#com.redhat.et.silex.sample.split.SplitSampleRDDFunctions
可以从开源silex项目获得方法:
https://github.com/willb/silex
一篇博客文章解释了它们的工作原理:
http://erikerlandson.github.io/blog/2016/02/08/efficient-multiplexing-for-spark-rdds/
def muxPartitions[U :ClassTag](n: Int, f: (Int, Iterator[T]) => Seq[U],
persist: StorageLevel): Seq[RDD[U]] = {
val mux = self.mapPartitionsWithIndex { case (id, itr) =>
Iterator.single(f(id, itr))
}.persist(persist)
Vector.tabulate(n) { j => mux.mapPartitions { itr => Iterator.single(itr.next()(j)) } }
}
def flatMuxPartitions[U :ClassTag](n: Int, f: (Int, Iterator[T]) => Seq[TraversableOnce[U]],
persist: StorageLevel): Seq[RDD[U]] = {
val mux = self.mapPartitionsWithIndex { case (id, itr) =>
Iterator.single(f(id, itr))
}.persist(persist)
Vector.tabulate(n) { j => mux.mapPartitions { itr => itr.next()(j).toIterator } }
}
正如其他地方所提到的,这些方法确实需要权衡内存以提高速度,因为它们通过计算整个分区结果来进行操作,并且急切地“#34;而不是"懒洋洋地。"因此,这些方法可能会在大型分区上遇到内存问题,而传统的惰性转换则不会。
答案 2 :(得分:1)
如果使用randomSplit API call拆分RDD,则会返回一组RDD。
如果您想要返回5个RDD,请传入5个权重值。
e.g。
.state("board", {
url:"/board/:type",
templateUrl: "boardListPlain.jsp",
controller: 'BoardController',
params: {
type: {
value: 'defaultType' //if you don't mention the type, defaultType will become default.
}
}
controllerAs : 'board'
})
.state("board.insert", {
url:"/insert",
templateUrl: "boardInsert.html"
})
答案 3 :(得分:0)
一种方法是使用自定义分区程序根据过滤条件对数据进行分区。这可以通过扩展Partitioner
并实施类似于RangePartitioner
的内容来实现。
然后可以使用映射分区从分区RDD构造多个RDD,而无需读取所有数据。
val filtered = partitioned.mapPartitions { iter => {
new Iterator[Int](){
override def hasNext: Boolean = {
if(rangeOfPartitionsToKeep.contains(TaskContext.get().partitionId)) {
false
} else {
iter.hasNext
}
}
override def next():Int = iter.next()
}
请注意,过滤后的RDD中的分区数与分区RDD中的分区数相同,因此应使用合并来减少此分区并删除空分区。