Spark基于Broadcast Join的三角形计数代码异常问题求助
三角形计数代码的问题分析
我需要基于(关注者follower,被关注者followee)的有向边数据计算三角形数量,采用Broadcast Join方式实现,将edgesRDD.collectAsMap()广播到所有节点。但运行代码时出现异常:
- 输入数据为
1,2 2,3 2,4 3,1时,预期trianglesRDD输出(1,2,3)、(2,1,3)、(3,1,2),实际仅得到(2,3,1) - 输入数据为
1,2 2,3 2,4 4,1时,能得到正确结果(1,2,4)、(2,1,4)、(2,4,1)
给出的代码如下:
val spark = SparkSession.builder() .appName("Combining in Spark") .config("spark.master", "local") .getOrCreate() import spark.implicits._ val edgesRDD = spark.sparkContext.textFile(args(0)) .map(line => { val parts = line.split(",") (parts(0).toInt, parts(1).toInt) // (follower, user) }) .filter { case (follower, user) => follower < maxValue && user < maxValue } val broadcastEdges = spark.sparkContext.broadcast(edgesRDD.collectAsMap()) val trianglesRDD = edgesRDD .flatMap { case (a, b) => broadcastEdges.value.get(b) match { case Some(c) if broadcastEdges.value.contains(c) && broadcastEdges.value(c) == a => Seq((a, b, c)) case _ => Seq.empty } } val triangleCount = trianglesRDD.count() / 3
核心问题分析
1. collectAsMap()丢失多值边数据
edgesRDD.collectAsMap()会将RDD中的键值对转换为Map[Int, Int],但Map的键具有唯一性——如果同一个关注者(follower)对应多个被关注者(followee),比如输入中的2,3和2,4,后续的键值对会直接覆盖前面的,最终Map中键2对应的value只会是4,完全丢失了2→3这条边的信息。
在第一个输入场景中,三角形1-2-3依赖的边2→3被覆盖,导致:
- 处理边
1→2时,从广播Map中获取2的followee得到4,无法找到c=3,无法生成对应三角形 - 处理边
3→1时,获取1的followee是2,检查2的followee是4≠3,不生成三角形 - 仅处理边
2→3时,获取3的followee是1,检查1的followee是2=a=2,生成(2,3,1)
而第二个输入的三角形1-2-4依赖的边1→2、2→4、4→1在Map中都未被覆盖(每个follower对应的followee唯一),所以能正确生成所有三个环条目。
2. 三角形匹配逻辑局限
代码中broadcastEdges.value(c) == a的判断,仅检查c的单个followee是否等于a,但即使解决了Map覆盖问题,这个逻辑也只能处理每个节点仅有一个关注对象的场景,无法处理一个节点关注多个对象的情况。
修复方案
- 将边数据转换为多值Map:用
groupByKey把同一个follower的所有followee聚合为集合,再广播这个多值Map,避免数据丢失 - 修改三角形匹配逻辑:遍历b的所有followee,检查每个c是否关注a
修复后的代码示例:
val spark = SparkSession.builder() .appName("Combining in Spark") .config("spark.master", "local") .getOrCreate() import spark.implicits._ val edgesRDD = spark.sparkContext.textFile(args(0)) .map(line => { val parts = line.split(",") (parts(0).toInt, parts(1).toInt) // (follower, user) }) .filter { case (follower, user) => follower < maxValue && user < maxValue } // 转换为键是follower,值是该follower关注的所有followee的集合 val followerToFollowees = edgesRDD.groupByKey().collectAsMap() val broadcastEdges = spark.sparkContext.broadcast(followerToFollowees) val trianglesRDD = edgesRDD .flatMap { case (a, b) => // 获取b的所有followee broadcastEdges.value.get(b) match { // 遍历b的每个followee c,检查c是否关注a case Some(cs) => cs.flatMap(c => if (broadcastEdges.value.get(c).exists(_.contains(a))) Seq((a, b, c)) else Seq.empty ) case _ => Seq.empty } } val triangleCount = trianglesRDD.count() / 3
内容的提问来源于stack exchange,提问作者Irene
相关产品推荐
相关产品推荐

