Spark代码组织和最佳实践


72

因此,在面向对象的世界中花了很多年并始终考虑代码重用,设计模式和最佳实践之后,我发现自己在Spark世界中在代码组织和代码重用方面有些挣扎。

如果我尝试以可重用的方式编写代码,那么它几乎总是会带来性能上的损失,最终我会将其重写为适合我的特定用例的最佳方式。这个常量“写出最适合该特定用例的内容”也会影响代码的组织,因为当“它们都真正属于一起”时,很难将代码拆分为不同的对象或模块,因此我最终只有很少的包含长代码的“上帝”对象复杂转换链。实际上,我经常认为,如果我回顾了当我在面向对象的世界中工作时所写的大部分Spark代码,我会退缩并将其视为“意大利面条式代码”。

我上网冲浪试图找到与面向对象世界的最佳做法相当的东西,但是运气不佳。我可以找到一些函数式编程的“最佳实践”,但是Spark只是增加了一层,因为性能是这里的主要因素。

所以我想问的是,您是否有任何Spark专家找到了一些您可以推荐的编写Spark代码的最佳实践?

编辑

正如评论中所写,我实际上并不希望有人发布有关如何解决该问题的答案,而是我希望这个社区中的某人遇到过一些Martin Fowler类型的人,他曾在某处写过som文章或博客文章。关于如何解决Spark世界中代码组织的问题。

@DanielDarabos建议我举一个例子说明代码组织和性能冲突的情况。虽然我发现我在日常工作中经常遇到此问题,但我很难将其归结为一个很好的最小示例;)但我会尝试的。

在面向对象的世界中,我是“单一责任原则”的忠实拥护者,因此,我将确保我的方法仅负责一件事。它使它们可重用且易于测试。因此,例如,如果我必须计算列表中某些数字的总和(与某些条件匹配),并且必须计算同一数字的平均值,那么我绝对可以创建两种方法-一种计算和,然后计算平均值。像这样:

def main(implicit args: Array[String]): Unit = {
  val list = List(("DK", 1.2), ("DK", 1.4), ("SE", 1.5))

  println("Summed weights for DK = " + summedWeights(list, "DK")
  println("Averaged weights for DK = " + averagedWeights(list, "DK")
}

def summedWeights(list: List, country: String): Double = {
  list.filter(_._1 == country).map(_._2).sum
}

def averagedWeights(list: List, country: String): Double = {
  val filteredByCountry = list.filter(_._1 == country) 
  filteredByCountry.map(_._2).sum/ filteredByCountry.length
}

我当然可以继续尊重Spark中的SRP:

def main(implicit args: Array[String]): Unit = {
  val df = List(("DK", 1.2), ("DK", 1.4), ("SE", 1.5)).toDF("country", "weight")

  println("Summed weights for DK = " + summedWeights(df, "DK")
  println("Averaged weights for DK = " + averagedWeights(df, "DK")
}


def avgWeights(df: DataFrame, country: String, sqlContext: SQLContext): Double = {
  import org.apache.spark.sql.functions._
  import sqlContext.implicits._

  val countrySpecific = df.filter('country === country)
  val summedWeight = countrySpecific.agg(avg('weight))

  summedWeight.first().getDouble(0)
}

def summedWeights(df: DataFrame, country: String, sqlContext: SQLContext): Double = {
  import org.apache.spark.sql.functions._
  import sqlContext.implicits._

  val countrySpecific = df.filter('country === country)
  val summedWeight = countrySpecific.agg(sum('weight))

  summedWeight.first().getDouble(0)
}

但是因为我df可能包含数十亿行,所以我宁愿不必执行filter两次。实际上,性能与EMR成本直接相关,因此我真的不希望如此。为了克服它,我决定违反SRP并简单地将两个功能合而为一,并确保我对经过国家过滤的调用保持不变DataFrame,如下所示:

def summedAndAveragedWeights(df: DataFrame, country: String, sqlContext: SQLContext): (Double, Double) = {
  import org.apache.spark.sql.functions._
  import sqlContext.implicits._

  val countrySpecific = df.filter('country === country).persist(StorageLevel.MEMORY_AND_DISK_SER)
  val summedWeights = countrySpecific.agg(sum('weight)).first().getDouble(0)
  val averagedWeights = summedWeights / countrySpecific.count()

  (summedWeights, averagedWeights)
}

现在,这个示例当然可以大大简化现实生活中遇到的情况。在这里,我可以简单地通过df 将其交给sum和avg函数(也将是更多的SRP)之前进行过滤和持久化来解决它,但是在现实生活中,可能需要进行多次中间计算。换句话说,filter此处的功能仅是尝试简单举例说明将从持久化中受益的事物。实际上,我认为persist在这里call是一个关键字。调用persist将大大加快我的工作速度,但是代价是我必须紧密耦合所有依赖于持久化的代码DataFrame-即使它们在逻辑上是分开的。


5
特别是任何一种语言?我根本不是专家,但是对于Java和Scala,我认为没有理由不按照自己的标准来构造代码。Databriks参考应用程序(github.com/databricks/reference-apps/tree/master/timeseries)是构建Spark项目的一个很好的开始。希望能帮助到你!
马可(Marco)

我探索了不同的方法,当我初次了解数据集时,您正在谈论的是这些意粉代码。然后,我考虑如何对正在使用的数据进行分类。它是如何发生变化的,等等。从那里开始,经典的软件设计模式往往对我有用。
Myles Baker

2
另外-对于如何在可重用的分布式环境中编写高效,可伸缩的代码,我还没有达成行业共识。这些模式通常与数据高度耦合,因此您必须努力使用公认的标准来创建数据。对于某些问题,这将永远不够高效。
Myles Baker

我完全理解这个问题,但是我认为它涵盖了所有“堆栈溢出”关闭原因,除了“重复”之外。您如何看待另一种放置方式?也许显示一个最小的例子,说明代码组织和性能冲突的目标,并询问如何解决该冲突。我认为这可以很好地补充当前的问题内容,​​并可以给出特定的答案。
Daniel Darabos

1
@DanielDarabos我理解您的评论,我正在考虑一个很好的例子来解决这个问题。
Glennie Helles Sindholt '16年

Answers:


22

12
我要感谢您提供的全面列表,我也同意您的观点,可以从其他人那里汲取很多教训(这是我为什么首先提出问题的原因;-)。但是,实际上,我觉得我对Spark的工作原理有很好的理解-这更多的是编写代码,不仅要在性能方面达到最佳,而且还可以使用良好的编码做法,例如将代码分开关注。至少到目前为止,我发现这两个概念或多或少是相互排斥的。
Glennie Helles Sindholt '16

@格兰妮,现在我和你一样。如果您可以分享如何克服这些问题,这将非常有帮助
Jeevan

2
我真的希望我能告诉您,我找到了克服它们的方法-我还没有:(我刚刚接受,函数式编程世界中的代码紧密耦合。唯一的好处是函数式编程,我写了很多,代码少了很多,所以重复代码比OOP中的问题少...
Glennie Helles Sindholt 18-10-19

带有Informatica或BODS的DWH和BI实际上仅具有可重用的LKP模块,如UDF imho这样的转换。我同意你的看法。
thebluephantom
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.