小知识:Spark RDD连接查询

339 阅读3分钟

小知识,大挑战!本文正在参与「程序员必备小知识」创作活动

假设我们有一个这样的表,我们需要做的事情是从这样一个表明父子关系的文件中,找出其中的祖孙关系的关系。

父子关系的表格像下面这样:

Steven Lucy
Steven Jack
Jone Lucy
Jone Jack
Lucy Mary
Lucy Frank
Jack Alice
Jack Jesse
David Alice
David Jesse
Philip David
Philip Alma
Mark David
Mark Alma

实际上如果我们学习过sql的话,把这个文件当做一个表可以明白这里这里的操作其实是一个简单的连接操作,以父母为中介,自身连接得到祖孙关系。

在sql学习中我们有个经典的问题,查询间接先修课(先修课的先修课),和这个问题本质是一样的。

不过不同的是我们这里只能进行流式批处理。

在sql中这个文件作为一个表可以轻易的取别名,然后把它当作一个表做查询操作,而这里我们要考虑下如何伪造一种有两个表的效果。

一种可行的办法是这样的,我们把表中的每一行都作为两个表中取出的元素(也就是正着一次,反着一次),交由下一个操作处理。其中我们把一个表的行的key设为孩子,另一个表的行的key设置成父母。

这样一来,孩子和父母最终会由同一个reduce操作处理,我们在这个阶段将两个表的元素做一个笛卡尔积操作,即可得到这部分的祖孙关系。

只是这其中还有一个问题在于,我们在reduce操作时,我们应该如何分辨来自两个不同的表的行,map直接提供两次相反的数据是不可行的。我们考虑在map操作时放置一个元数据在行上,用于表明它来自的表。这样reduce就可以分辨出数据来源,以便做笛卡尔积操作了。

具体到代码上我们可以这样写:

    val result = textFile.flatMap { line =>
      val arr = line.split(" ")
      val child = arr(0)
      val parent = arr(1)
      Iterable((parent, 0, child), (child, 1, parent))//0 1 作为元数据,表明我们虚拟的表来源
    }.groupBy(_._1)
      .flatMap { x =>
        val left = x._2.filter(_._2 == 0) // 左表
        val right = x._2.filter(_._2 == 1) // 右表
        left.flatMap(
          x => right.map(y => s"${x._3},${y._3}")
        )
      }
     result.collect().foreach(println)

提交到 spark 运行之后可以看到如下输出:

Philip,Alice
Philip,Jesse
Mark,Alice
Mark,Jesse
Steven,Mary
Steven,Frank
Jone,Mary
Jone,Frank
Steven,Alice
Steven,Jesse
Jone,Alice
Jone,Jesse

小结:

为了让流式批处理能够模拟出两个效果,我们姑且在map上刷了些小把戏,在数据上附上一个标志,然后分成左右两个表,让reduce得到每个相同的key(这里也就是孩子的父母与父母的孩子相同)的所有值,然后通过把二者分离之后做笛卡尔积得到最终结果。从实现上来说,有一些牵强,与spark的另一些高级api相比甚是丑陋,不过最终还是完成了我们需要的效果。