Ela*_*dor 5 apache-spark spark-streaming apache-spark-dataset
我们有一个 spark 流应用程序(spark 2.1 在 Hortonworks 2.6 上运行)并使用DataSet.repartition(在DataSet<Row>从 Kafka 读取的a上)DataSet<Row>'s根据给定的列(称为block_id)重新分区分区。
我们从一个DataSet<Row>包含 50 个分区的分区开始,最后(在调用 之后DataSet.repartition)分区数量等于唯一block_id 的数量。
问题是它的DataSet.repartition行为不像我们预期的那样——当我们查看运行 的 spark 作业的事件时间线时repartition,我们看到有几个任务处理 1 个block_id,而处理 2 个block_id甚至 3 或 4 个block_id 的任务较少。
似乎可以DataSet.repartition确保所有Rows具有相同block_id 的都在单个分区内,但并不是每个创建分区的任务都只处理一个 block_id。
结果是重新分区作业(在流应用程序内部运行)花费的时间与其最长的任务(处理最多block_id 的任务)一样多。
我们尝试使用提供给流媒体应用程序的 Vcores 数量 - 从 10 到 25 到 50(我们在从 Kafka 读取的原始 RDD 中有 50 个分区)但结果是相同的 - 总是有一个或多个任务处理更多比一个block_id。
我们甚至尝试增加批处理时间,这同样没有帮助我们实现处理一个block_id 的任务的目标。
举个例子 - 这是描述repartitionspark 作业运行的事件时间线和任务表:
事件时间线- 红色的两个任务是处理两个block_id 的任务:
任务表- 红色的两个任务与上面的两个任务相同 - 注意每个任务的持续时间是所有其他任务(仅处理一个block_id)的持续时间的两倍
这对我们来说是一个问题,因为流应用程序由于这些长任务而延迟,我们需要一种解决方案,使我们能够repartition在 DataSet上执行,同时让每个任务只处理一个block_id。
如果这是不可能的,那么也许这是可能的,JavaRDD?因为在我们的情况下,DataSet<Row>我们运行的repartitionon 是从JavaRDD.
您需要考虑的2个问题:
正如您所见,对 DataFrame 进行简单的重新分区并不能保证您会获得均匀分布。当您通过 block_id 重新分区时,它将使用 HashPartitioner,其公式为:
Utils.nonNegativeMod(key.hashCode, numPartitions)
Run Code Online (Sandbox Code Playgroud)
参见:https : //github.com/apache/spark/blob/branch-2.2/core/src/main/scala/org/apache/spark/Partitioner.scala#L80-L88
很可能有 2 个以上的键被分配给相同的 partition_id,因为 partition_id 是键的 hashCode 模数分区。
您可以通过使用带有自定义分区程序的 RDD 来实现您的需求。最简单的方法是在重新分区之前提取不同的 block_id 列表。
这是一个简单的例子。假设您可以有 5 个块 (2,3,6,8,9) 并且您的集群有 8 个执行程序(最多可以同时运行 8 个任务),我们被 3 个执行程序过度配置:
scala> spark.conf.get("spark.sql.shuffle.partitions")
res0: String = 8
scala> spark.conf.get("spark.default.parallelism")
res1: String = 8
// Basic class to store dummy records
scala> case class MyRec(block_id: Int, other: String)
defined class MyRec
// Sample DS
scala> val ds = List((2,"A"), (3,"X"), (3, "B"), (9, "Y"), (6, "C"), (9, "M"), (6, "Q"), (2, "K"), (2, "O"), (6, "W"), (2, "T"), (8, "T")).toDF("block_id", "other").as[MyRec]
ds: org.apache.spark.sql.Dataset[MyRec] = [block_id: int, other: string]
scala> ds.show
+--------+-----+
|block_id|other|
+--------+-----+
| 2| A|
| 3| X|
| 3| B|
| 9| Y|
| 6| C|
| 9| M|
| 6| Q|
| 2| K|
| 2| O|
| 6| W|
| 2| T|
| 8| T|
+--------+-----+
// Default partitioning gets data distributed as uniformly as possible (record count)
scala> ds.rdd.getNumPartitions
res3: Int = 8
// Print records distribution by partition
scala> ds.rdd.mapPartitionsWithIndex((idx, it) => Iterator((idx, it.toList))).toDF("partition_id", "block_ids").show
+------------+--------------+
|partition_id| block_ids|
+------------+--------------+
| 0| [[2,A]]|
| 1|[[3,X], [3,B]]|
| 2| [[9,Y]]|
| 3|[[6,C], [9,M]]|
| 4| [[6,Q]]|
| 5|[[2,K], [2,O]]|
| 6| [[6,W]]|
| 7|[[2,T], [8,T]]|
+------------+--------------+
// repartitioning by block_id leaves 4 partitions empty and assigns 2 block_ids (6,9) to same partition (1)
scala> ds.repartition('block_id).rdd.mapPartitionsWithIndex((idx, it) => Iterator((idx, it.toList))).toDF("partition_id", "block_ids").where(size('block_ids) > 0).show(false)
+------------+-----------------------------------+
|partition_id|block_ids |
+------------+-----------------------------------+
|1 |[[9,Y], [6,C], [9,M], [6,Q], [6,W]]|
|3 |[[3,X], [3,B]] |
|6 |[[2,A], [2,K], [2,O], [2,T]] |
|7 |[[8,T]] |
+------------+-----------------------------------+
// Create a simple mapping for block_id to partition_id to be used by our custom partitioner (logic may be more elaborate or static if the list of block_ids is static):
scala> val mappings = ds.map(_.block_id).dropDuplicates.collect.zipWithIndex.toMap
mappings: scala.collection.immutable.Map[Int,Int] = Map(6 -> 1, 9 -> 0, 2 -> 3, 3 -> 2, 8 -> 4)
//custom partitioner assigns partition_id according to the mapping arg
scala> class CustomPartitioner(mappings: Map[Int,Int]) extends org.apache.spark.Partitioner {
| override def numPartitions: Int = mappings.size
| override def getPartition(rec: Any): Int = { mappings.getOrElse(rec.asInstanceOf[Int], 0) }
| }
defined class CustomPartitioner
// Repartition DS using new partitioner
scala> val newDS = ds.rdd.map(r => (r.block_id, r)).partitionBy(new CustomPartitioner(mappings)).toDS
newDS: org.apache.spark.sql.Dataset[(Int, MyRec)] = [_1: int, _2: struct<block_id: int, other: string>]
// Display evenly distributed block_ids
scala> newDS.rdd.mapPartitionsWithIndex((idx, it) => Iterator((idx, it.toList))).toDF("partition_id", "block_ids").where(size('block_ids) > 0).show(false)
+------------+--------------------------------------------+
|partition_id|block_ids |
+------------+--------------------------------------------+
|0 |[[9,[9,Y]], [9,[9,M]]] |
|1 |[[6,[6,C]], [6,[6,Q]], [6,[6,W]]] |
|2 |[[3,[3,X]], [3,[3,B]]] |
|3 |[[2,[2,A]], [2,[2,K]], [2,[2,O]], [2,[2,T]]]|
|4 |[[8,[8,T]]] |
+------------+--------------------------------------------+
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3123 次 |
| 最近记录: |