Apache spark 如何将一个RDD拆分为两个或多个RDD?

Apache spark 如何将一个RDD拆分为两个或多个RDD?,apache-spark,pyspark,rdd,Apache Spark,Pyspark,Rdd,我正在寻找一种将RDD拆分为两个或更多RDD的方法。我见过的最近的一个仍然是一个RDD 如果您熟悉SAS,请执行以下操作: data work.split1, work.split2; set work.preSplit; if (condition1) output work.split1 else if (condition2) output work.split2 run; 这导致了两个不同的数据集。它必须立即持久化才能得到我想要

我正在寻找一种将RDD拆分为两个或更多RDD的方法。我见过的最近的一个仍然是一个RDD

如果您熟悉SAS,请执行以下操作:

data work.split1, work.split2;
    set work.preSplit;

    if (condition1)
        output work.split1
    else if (condition2)
        output work.split2
run;

这导致了两个不同的数据集。它必须立即持久化才能得到我想要的结果…

不可能从一个转换中产生多个RDD*。如果要拆分RDD,必须为每个拆分条件应用
过滤器。例如:

def偶数(x):返回x%2==0
def奇数(x):返回非偶数(x)
rdd=sc.并行化(范围(20))
rdd_奇数,rdd_偶数=(rdd.filter(f)表示f in(奇数,偶数))
如果您只有一个二进制条件,并且计算成本很高,您可能会喜欢这样:

data work.split1, work.split2;
    set work.preSplit;

    if (condition1)
        output work.split1
    else if (condition2)
        output work.split2
run;
kv_rdd=rdd.map(λx:(x,奇数(x)))
kv_rdd.cache()
rdd_奇数=千伏滤波器(λ千伏:千伏[1])。键()
rdd_偶数=千伏rdd.滤波器(λ千伏:非千伏[1])。键()
这意味着只有一个谓词计算,但需要对所有数据进行额外的传递

重要的是要注意,只要输入RDD被正确缓存,并且没有关于数据分布的额外假设,重复过滤和带有嵌套if-else的for循环之间的时间复杂度就没有显著差异

对于N个元素和M个条件,您必须执行的操作数显然与N乘以M成正比。对于for循环,它应该更接近(N+MN)/2,重复的过滤器正好是NM,但在一天结束时,它只不过是O(NM)。你可以看到我与**的讨论,了解一些利弊

在非常高的水平上,你应该考虑两件事:

  • Spark转换是懒惰的,除非您执行一个操作,否则您的RDD不会具体化

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

     rdd_odd, rdd_even = (rdd.filter(f) for f in (odd, even))
    
    如果后来我决定只需要
    rdd_奇数
    ,那么就没有理由实现
    rdd_偶数

    如果查看SAS示例以计算
    work.split2
    ,则需要具体化输入数据和
    work.split1

  • RDD提供声明式API。当您使用
    过滤器
    映射
    时,这完全取决于Spark engine如何执行此操作。只要传递给转换的函数没有副作用,它就创造了优化整个管道的多种可能性

  • 归根结底,这种情况还不足以证明其自身的转变是正确的

    这张带有滤镜图案的地图实际上用于核心火花。请参阅我对
    randomspilt
    方法的回答和说明

    如果唯一的目标是在输入时实现拆分,则可以为
    DataFrameWriter
    使用
    partitionBy
    子句来指定以下哪种文本输出格式:

    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可以是原子类型或/元组(K,V)。任何其他操作都必须使用上述的一些组合来表示。您可以查看更多详细信息

    **


    ***另请参见

    如果使用拆分一个RDD,则返回一个RDD数组

    如果希望返回5个RDD,请传入5个权重值

    e、 g


    一种方法是使用自定义分区器根据您的筛选条件对数据进行分区。这可以通过扩展
    Partitioner
    并实现类似于
    RangePartitioner
    的东西来实现

    然后,可以使用映射分区从已分区的RDD构造多个RDD,而无需读取所有数据

    val filtered=partitioned.mapPartitions{iter=>{
    新迭代器[Int](){
    覆盖def hasNext:Boolean={
    if(rangeOfPartitionsToKeep.contains(TaskContext.get().partitionId)){
    假的
    }否则{
    国际热核聚变实验堆
    }
    }
    覆盖def next():Int=iter.next()
    }
    

    请注意,过滤后的RDD中的分区数将与已分区的RDD中的分区数相同,因此应使用合并来减少分区数并删除空分区。

    与上面提到的其他海报一样,没有单个、本机RDD转换来拆分RDD,但这里有一些“多路复用”可有效模拟RDD上各种“拆分”的操作,无需多次读取:

    一些特定于随机拆分的方法:

    方法可从开源silex项目获得:

    解释其工作原理的博客帖子:

    def muxPartitions[U:ClassTag](n:Int,f:(Int,迭代器[T])=>Seq[U],
    持久化:存储级别):Seq[RDD[U]={
    val mux=self.mapPartitionsWithIndex{case(id,itr)=>
    单个迭代器(f(id,itr))
    }.坚持(坚持)
    Vector.tablate(n){j=>mux.mapPartitions{itr=>Iterator.single(itr.next()(j))}
    }
    def flatMuxPartitions[U:ClassTag](n:Int,f:(Int,迭代器[T])=>Seq[TraversableOnce[U]],
    持久化:存储级别):Seq[RDD[U]={
    val mux=self.mapPartitionsWithIndex{case(id,itr)=>
    单个迭代器(f(id,itr))
    }.坚持(坚持)
    Vector.tablate(n){j=>mux.mapPartitions{itr=>itr.next()(j).toIterator}
    }
    

    正如其他地方提到的,这些方法确实需要在速度上权衡内存,因为它们通过“急切地”而不是“懒散地”计算整个分区结果来运行因此,这些方法可能会在大分区上遇到内存问题,而传统的惰性转换则不然。

    这难道不是@zero323的解决方案吗?他说它会多次读取它,这是我试图避免的。然而,每次调用mapPartitions时,它都会为每个分区运行任务分区内的实际数据(如果只读取onceok,但如果我将其保留为immed)