Quando é a dependência ampla de junção e dependência estreita do Spark
Pergunta:
Consulte o seguinte código:
1. Qual é o resultado das duas instruções de impressão, se a dependência correspondente é dependência ampla ou dependência estreita e por que esse resultado;
2. Operação de junção Quando é a dependência ampla e quando é a dependência estreita;
import org.apache.spark.rdd.RDD
import org.apache.spark.{
HashPartitioner, SparkConf, SparkContext}
object JoinDemo2 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName(this.getClass.getCanonicalName.init).setMaster("local[*]")
val sc = new SparkContext(conf)
sc.setLogLevel("WARN")
val random = scala.util.Random
val col1 = Range(1, 50).map(idx => (random.nextInt(10), s"user$idx"))
val col2 = Array((0, "BJ"), (1, "SH"), (2, "GZ"), (3, "SZ"), (4, "TJ"), (5, "CQ"), (6, "HZ"), (7, "NJ"), (8, "WH"), (0,
"CD"))
val rdd1: RDD[(Int, String)] = sc.makeRDD(col1)
val rdd2: RDD[(Int, String)] = sc.makeRDD(col2)
val rdd3: RDD[(Int, (String, String))] = rdd1.join(rdd2)
rdd3.count()
println(rdd3.dependencies)
val rdd4: RDD[(Int, (String, String))] = rdd1.partitionBy(new HashPartitioner(3)).join(rdd2.partitionBy(new
HashPartitioner(3)))
rdd4.count()
println(rdd4.dependencies)
Thread.sleep(10000000)
sc.stop()
}}
Resposta:
1. Duas instruções de impressão: List(org.apache.spark.OneToOneDependency@63acf8f6) List(org.apache.spark.OneToOneDependency@d9a498)
Dependências correspondentes:
rdd3 corresponde a dependências amplas e rdd4 corresponde a dependências estreitas.
Motivos:
1) Consulte o webUI
do diagrama DAG. Pode-se ver que a primeira junção está claramente separada do anterior. Satge. Percebe-se que se trata de uma dependência ampla. A segunda junção, junção após partitionBy, não é dividida em um estágio separadamente, o que mostra que é uma dependência estreita.
rdd3 join
rdd4 join
2) Análise de código:
a. Primeiro é o método de junção padrão, aqui um particionador padrão é usado
/**
* Return an RDD containing all pairs of elements with matching keys in `this` and `other`. Each
* pair of elements will be returned as a (k, (v1, v2)) tuple, where (k, v1) is in `this` and
* (k, v2) is in `other`. Performs a hash join across the cluster.
*/
def join[W](other: RDD[(K, W)]): RDD[(K, (V, W))] = self.withScope {
join(other, defaultPartitioner(self, other))
}
b. O particionador padrão, para a primeira junção retornará um HashPartitioner com o número total de núcleos de computador como o número de partições. A segunda junção retornará o HashPartitioner que definimos (partição número 3)
def defaultPartitioner(rdd: RDD[_], others: RDD[_]*): Partitioner = {
val rdds = (Seq(rdd) ++ others)
val hasPartitioner = rdds.filter(_.partitioner.exists(_.numPartitions > 0))
val hasMaxPartitioner: Option[RDD[_]] = if (hasPartitioner.nonEmpty) {
Some(hasPartitioner.maxBy(_.partitions.length))
} else {
None
}
val defaultNumPartitions = if (rdd.context.conf.contains("spark.default.parallelism")) {
rdd.context.defaultParallelism
} else {
rdds.map(_.partitions.length).max
}
// If the existing max partitioner is an eligible one, or its partitions number is larger
// than the default number of partitions, use the existing partitioner.
if (hasMaxPartitioner.nonEmpty && (isEligiblePartitioner(hasMaxPartitioner.get, rdds) ||
defaultNumPartitions < hasMaxPartitioner.get.getNumPartitions)) {
hasMaxPartitioner.get.partitioner.get
} else {
new HashPartitioner(defaultNumPartitions)
}
}
c. Vá para a execução real do método de junção, em que flatMapValues é uma dependência estreita, portanto, se houver uma dependência ampla, ela deve estar no operador de cogrupo
/**
* Return an RDD containing all pairs of elements with matching keys in `this` and `other`. Each
* pair of elements will be returned as a (k, (v1, v2)) tuple, where (k, v1) is in `this` and
* (k, v2) is in `other`. Uses the given Partitioner to partition the output RDD.
*/
def join[W](other: RDD[(K, W)], partitioner: Partitioner): RDD[(K, (V, W))] = self.withScope {
this.cogroup(other, partitioner).flatMapValues( pair =>
for (v <- pair._1.iterator; w <- pair._2.iterator) yield (v, w)
)
}
d. Digite o método do cogrupo, o núcleo é CoGroupedRDD, de acordo com a necessidade de dois para juntar RDD e um particionador. Na primeira junção, nenhum dos rdd tem um particionador, portanto, nesta etapa, os dois rdds precisam executar um embaralhamento com base no particionador de entrada, portanto, a primeira junção é uma dependência ampla. A segunda junção foi dividida em zonas neste momento e não há necessidade de embaralhar novamente. Portanto, o segundo é a dependência estreita
/**
* For each key k in `this` or `other`, return a resulting RDD that contains a tuple with the
* list of values for that key in `this` as well as `other`.
*/
def cogroup[W](other: RDD[(K, W)], partitioner: Partitioner)
: RDD[(K, (Iterable[V], Iterable[W]))] = self.withScope {
if (partitioner.isInstanceOf[HashPartitioner] && keyClass.isArray) {
throw new SparkException("HashPartitioner cannot partition array keys.")
}
val cg = new CoGroupedRDD[K](Seq(self, other), partitioner)
cg.mapValues {
case Array(vs, w1s) =>
(vs.asInstanceOf[Iterable[V]], w1s.asInstanceOf[Iterable[W]])
}
}
e) Ambos imprimem OneToOneDependency, pois em CoGroupedRDD, no método getDependencies, se o rdd tiver um particionador, ele retornará OneToOneDependency (rdd).
override def getDependencies: Seq[Dependency[_]] = {
rdds.map {
rdd: RDD[_] =>
if (rdd.partitioner == Some(part)) {
logDebug("Adding one-to-one dependency with " + rdd)
new OneToOneDependency(rdd)
} else {
logDebug("Adding shuffle dependency with " + rdd)
new ShuffleDependency[K, Any, CoGroupCombiner](
rdd.asInstanceOf[RDD[_ <: Product2[K, _]]], part, serializer)
}
}
}
- Quando a junção é uma dependência ampla e quando é uma dependência estreita?
A partir da análise acima, pode-se saber que se as duas tabelas a serem unidas já possuem um particionador e o número de partições é o mesmo, a mesma chave está na mesma partição neste momento. É uma dependência estreita. Por outro lado, se não houver partições nas duas tabelas que precisem ser unidas ou o número de partições for diferente e o embaralhamento for necessário ao unir, então é uma grande dependência