我正在使用Spark RDD加入两个大数据集。一个数据集非常偏斜,因此很少有执行程序任务需要很长时间才能完成工作。我该如何解决这个问题?
答案 0 :(得分:19)
关于如何做到的非常好的文章:https://datarus.wordpress.com/2015/05/04/fighting-the-skew-in-spark/
简短版本:
答案 1 :(得分:10)
根据您遇到的特定偏差类型,可能有不同的方法来解决它。基本思路是:
如果偏斜的数据参与了连接,那么LiMuBei的回答中引用的“反对火花中的偏见”一文是一种很好的技巧。在我的例子中,偏差是由连接列中的大量空值引起的。空值没有参与连接,但是由于连接列上的Spark分区,后连接分区非常偏斜,因为有一个包含所有空值的巨大分区。
我通过添加一个新列来解决它,该列将所有空值更改为分布均匀的临时值,例如“NULL_VALUE_X”,其中X被替换为介于1和10,000之间的随机数,例如, (在Java中):
// Before the join, create a join column with well-distributed temporary values for null swids. This column
// will be dropped after the join. We need to do this so the post-join partitions will be well-distributed,
// and not have a giant partition with all null swids.
String swidWithDistributedNulls = "swid_with_distributed_nulls";
int numNullValues = 10000; // Just use a number that will always be bigger than number of partitions
Column swidWithDistributedNullsCol =
when(csDataset.col(CS_COL_SWID).isNull(), functions.concat(
functions.lit("NULL_SWID_"),
functions.round(functions.rand().multiply(numNullValues)))
)
.otherwise(csDataset.col(CS_COL_SWID));
csDataset = csDataset.withColumn(swidWithDistributedNulls, swidWithDistributedNullsCol);
然后加入这个新列,然后加入:
outputDataset.drop(swidWithDistributedNullsCol);
答案 2 :(得分:9)
假设您必须在A.id = B.id上联接两个表A和B。假设表A在id = 1上有偏斜。
即从A加入B的A.id = B.id
有两种基本方法可以解决倾斜连接问题:
将查询/数据集分为两部分-一部分仅包含倾斜,而另一部分包含非倾斜的数据。 在上面的例子中。查询将变为-
1. select A.id from A join B on A.id = B.id where A.id <> 1;
2. select A.id from A join B on A.id = B.id where A.id = 1 and B.id = 1;
第一个查询不会有任何偏斜,因此ResultStage的所有任务将大致同时完成。
如果我们假设B只有很少的行且B.id = 1,那么它将适合内存。因此,第二查询将转换为广播联接。在Hive中,这也称为Map-side join。
参考:https://cwiki.apache.org/confluence/display/Hive/Skewed+Join+Optimization
然后可以将两个查询的部分结果合并以获得最终结果。
以上在LeMuBei中也提到过,第二种方法试图通过附加额外的列来随机化连接键。 步骤:
在较大的表(A)中添加一列,例如skewLeft,并为所有行填充0到N-1之间的随机数。
在较小的表(B)中添加一列,例如skewRight。将较小的表复制N次。因此,对于每个原始数据副本,新的 skewRight 列中的值将在0到N-1之间变化。为此,您可以使用explode sql / dataset运算符。
在1和2之后,将2个数据集/表连接,连接条件更新为-
*A.id = B.id && A.skewLeft = B.skewRight*
参考:https://datarus.wordpress.com/2015/05/04/fighting-the-skew-in-spark/
答案 3 :(得分:2)
Apache DataFu 有两种方法可以进行倾斜连接,它们实现了前面答案中的一些建议。
joinSkewed 方法进行加盐(添加随机数列以拆分偏斜值)。
broadcastJoinSkewed 方法适用于将数据帧划分为倾斜部分和规则部分的情况,如 Approach 2 的答案中的 moriarty007 所述。
DataFu 中的这些方法对于使用 Spark 2.x 的项目很有用。如果您已经在使用 Spark 3,则有 dedicated methods for doing skewed joins。
完全披露 - 我是 Apache DataFu 的成员。
答案 4 :(得分:1)
从https://datarus.wordpress.com/2015/05/04/fighting-the-skew-in-spark/获取参考 以下是使用Pyspark数据框API消除火花中的偏斜的代码
创建2个数据框:
from math import exp
from random import randint
from datetime import datetime
def count_elements(splitIndex, iterator):
n = sum(1 for _ in iterator)
yield (splitIndex, n)
def get_part_index(splitIndex, iterator):
for it in iterator:
yield (splitIndex, it)
num_parts = 18
# create the large skewed rdd
skewed_large_rdd = sc.parallelize(range(0,num_parts), num_parts).flatMap(lambda x: range(0, int(exp(x))))
skewed_large_rdd = skewed_large_rdd.mapPartitionsWithIndex(lambda ind, x: get_part_index(ind, x))
skewed_large_df = spark.createDataFrame(skewed_large_rdd,['x','y'])
small_rdd = sc.parallelize(range(0,num_parts), num_parts).map(lambda x: (x, x))
small_df = spark.createDataFrame(small_rdd,['a','b'])
对于大型df,将数据分为100个bin,对小型df进行100次复制
salt_bins = 100
from pyspark.sql import functions as F
skewed_transformed_df = skewed_large_df.withColumn('salt', (F.rand()*salt_bins).cast('int')).cache()
small_transformed_df = small_df.withColumn('replicate', F.array([F.lit(i) for i in range(salt_bins)]))
small_transformed_df = small_transformed_df.select('*', F.explode('replicate').alias('salt')).drop('replicate').cache()
最后,连接避免了偏斜
t0 = datetime.now()
result2 = skewed_transformed_df.join(small_transformed_df, (skewed_transformed_df['x'] == small_transformed_df['a']) & (skewed_transformed_df['salt'] == small_transformed_df['salt']) )
result2.count()
print "The direct join takes %s"%(str(datetime.now() - t0))
答案 5 :(得分:-1)
您可以尝试将“倾斜的”RDD重新分区到更多分区,或尝试增加spark.sql.shuffle.partitions
(默认情况下为200)。
在您的情况下,我会尝试将分区数设置为远高于执行程序的数量。