如何将RDD拆分为两个或更多RDD?

时间:2015-10-06 13:02:49

标签: apache-spark pyspark rdd

我正在寻找一种将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;

导致两个不同的数据集。必须立即坚持以获得我想要的结果......

4 个答案:

答案 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查看我的讨论**,了解一些利弊。

在非常高的层次上你应该考虑两件事:

  1. Spark转换是懒惰的,直到您执行RDD未实现的操作

    为什么重要?回到我的例子:

    rdd_odd, rdd_even = (rdd.filter(f) for f in (odd, even))
    

    如果稍后我决定只需要rdd_odd,那么就没有理由实现rdd_even

    如果您查看SAS示例来计算work.split2,则需要实现输入数据和work.split1

  2. RDD提供声明性API。当您使用filtermap时,完全取决于Spark引擎如何执行此操作。只要传递给转换的函数是免费的副作用,它就会创建多种优化整个管道的可能性。

  3. 在一天结束时,这个案例并不足以证明自己的转型是正确的。

    这个带有过滤器模式的地图实际上是在核心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种基本类型的转换:

    • RDD [T] => RDD [T]
    • RDD [T] => RDD [U]
    • (RDD [T],RDD [U])=> RDD [W]

    其中T,U,W可以是原子类型,也可以是products /元组(K,V)。必须使用上述的某些组合来表达任何其他操作。您可以查看the original RDD paper了解详情。

    ** http://chat.stackoverflow.com/rooms/91928/discussion-between-zero323-and-jason-lenderman

    ***另见Scala Spark: Split collection into several RDD?

答案 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中的分区数相同,因此应使用合并来减少此分区并删除空分区。