编写Spark:Scala与Java

Background

我于2019年4月上旬加入一个团队。他们正在编写Spark职位,以在Scala中做一系列不同的事情。 那时,我只了解Java,很少了解Scala,也几乎不了解Spark。 时光倒流到今天,现在我在Scala和Spark已有几个月的经验。

最近,我被安排在一个项目中,该项目扫描一个HBase表,对数据进行一些处理,然后将其写入另一个HBase表。 在Scala中轻松自在,对吧? 几周后,我完成了第一份工作。 红旗飘扬。 它是用Scala而不是Java编写的! Scala还没有被接受为公司想要“投资”的语言。 几乎没有人会说这种语言,一旦交完工作便无法支持。 我需要用Java重新编写作业,因此这使我转到了这篇博客文章。

这篇博客文章的重点是记录我用Java编写Spark作业的经历和经历。 是的,虽然可以用Java绝对编写Spark作业,但是如果可以代替使用Scala,则还应该查看必须编写的LESS数量。 如果您以任何身份使用Spark,为什么您的公司应该在Scala进行投资,这篇文章也可以用作弹药。 同样,完全公开后,将以下Java代码与Scala进行比较时,可能绝对会有更好的方法来编写以下Java代码。 因此,如果您看到做得不好的事情,请随时发表评论,并以更好的方式教育我。

现在,让我们深入探讨这个职位。

Why You Should Use Scala for Your Spark Jobs

SPARK

Right off the bat, if your company is using Spark, they should be able to support using Scala. Spark was written in Scala. If you are looking forward to a new feature in Spark, there is a chance you are going to get that feature first in Scala and later in other languages.

The Spark Shell is another big reason. The Spark Shell is basically a Scala REPL that lets you interact with the Spark API. Do you know that you have to scan an HBase table but can't quite think of the correct syntax to do it? Open up the Spark Shell, type in some Scala, and you will find out pretty quick whether or not you are on the right track.

使用Java,没有简单的方法。 根据我的经验,它是在Internet上四处寻找答案,编写代码,将代码打包到JAR中,将其传输到我可以将其上传到我们的集群中并运行(以实现最佳效果)的方式。

Coding

好吧,足够的整体细节。 让我们深入研究一些代码,我认为您在用Java编写Spark作业时会遇到最大的麻烦。

DataFrames & DataSets

DataFrames被组织成命名列的数据。 它们是不可变的分布式数据集合。

数据集是DataFrame API的扩展。 它提供类型安全的OOP接口。

More about the two can be found here.

因此,假设您刚刚扫描了HBase表,并且有一个表行对象行数。 您正在尝试从该表中获取特定数据(基于族和限定符)并将其放入DataSet中。 你会怎么做?

斯卡拉

val rowsDS = 
  rows.map(kv => (Bytes.toString(kv._1.get()),
    Bytes.toString(kv._2.getValue(Bytes.toBytes("family"), Bytes.toBytes("qualifier"))))
  ).toDS()

就像魔术一样.toDS()方法就是您所需要的。

Java呢?

Java,假设您有一个称为的JavaPairRDD行RDD已经设置好您的扫描结果。火花这里的变量是SparkSession需要设置您的工作。

Dataset<Row> rowsDS = spark.createDataFrame(rowRDD, MyRowSchema.class);

凉! 一行! 等等,第二个参数是什么? 为什么需要上课? 嗯,需要将特定的架构应用于RDD才能将其转换为DataSet。 还记得上面提到的DataSet是类型安全的吗? 因此,还需要执行额外的步骤,例如映射您的行RDD结果变成类型的对象MyRowSchema。

JavaRDD<MyRowSchema> javaRDD = rowRDD.map((Function<Tuple2<ImmutableBytesWritable, Result>, String>) tuple -> {
Result result = tuple._2;

return new MyRowSchema(Bytes.toString(result.getValue(Bytes.toBytes("family"), Bytes.toBytes("qualifier"))));

});

Dataset<Row> rowsDS = spark.createDataFrame(javaRDD, MyRowSchema.class);

由于使用Java样板,因此需要编写更多代码,并且需要通过一些辅助操作createDataFrame在Java中比使用简单.toDS()您在Scala中获得的方法。

Transformations

Transformations are a cool way for chaining custom transformations, like adding a column of data to a DataSet. See this awesome blog post for more details on Transformations.

假设您想让所有人都在一个名称为“ Ryan”及其年龄的数据框中... Scala让您首先定义方法,然后将它们链接在一起。

def retrieveAllRyansAge()(df: DataFrame): DataFrame = {
  df.filter(col("name").startsWith("Ryan"))
    .select("name", "age")
}
val ryansAgeDF = peoplesAgeDF
  .transform(RyanTransformer.retrieveAllRyansAge)

您完成了!

如果到目前为止您一直在关注,您可以猜测Java等效版本将更长一些...

public static Function1<Dataset<Row>, Dataset<Row>> retrieveAllRyansAge = new AbstractFunction1<Dataset<Row>, Dataset<Row>>() {
    @Override
    public Dataset<Row> apply(Dataset<Row> peopleDS) {
        return retrieveRyansWithAge(peopleDS);
    }
};

private static Dataset<Row> retrieveRyansWithAge(Dataset<Row> df) {
    return df.filter(col("name").startsWith("Ryan"))
            .select("name", "age");
}

当然,我确实添加了一种额外的方法来分离实际工作,因此从理论上讲您可以采用上述一种方法。

现在是实际的转变

Dataset<Row> ryansAgeDF = peoplesAgeDF
        .transform(RyanTransformer.retrieveAllRyansAge);
User Defined Functions

编写Spark作业的另一个方便项是用户定义函数(UDF)。 这些是用户编写的函数,正如您可能通过名称猜测的那样,有时内置函数无法满足需要。

例如,如果您需要从3个字母的语言代码转换为2个字母的语言代码,例如 “ eng”到“ en”。 您可以在Scala中编写类似下面的内容。

def getFormattedLanguage(lang: String): String = {

  Option(lang).map({
    l =>
      l.replaceAll(" ", "").toLowerCase match {
        case "eng" => "en"
        case "fre" => "fr"
        case _ => "en"
      }
  }).getOrElse("en")
}

val getFormattedLanguageUDF = udf[String, String] (getFormattedLanguage)

val formattedLanguages = languagesDF.withColumn("formattedLanguage", LanguageFormatter.getFormattedLanguageUDF(col("language")))

在Java中有一种特殊的调用UDF的方法。 类似于以下内容:

spark.udf().register(UDF_FORMATTED_LANG, (UDF1<String, String>) LanguageFormatter::getFormattedLanguage, DataTypes.StringType);

Dataset<Row> formattedLanguages = languagesDF.withColumn("formattedLanguage", callUDF(UDF_FORMATTED_LANG, col("language")));

哪里UDF_FORMATTED_LANG是具有UDF名称的字符串。

Unit Testing

我想讲的最后一个话题也是我最亲近的,是对您为Spark作业编写的代码进行单元测试,无论它是Java还是Scala。

以上面为转换编写的示例为例,如果我想从DataFrame中获取所有Ryan及其年龄,并计算测试中有多少,则如下所示:

trait SparkSessionTestWrapper {
  lazy val spark: SparkSession = {
    SparkSession
      .builder()
      .master("local")
      .appName("spark test example")
      .getOrCreate()
  }
}

下面的类需要上面的特征:

class NameAndAgeExtractorTest extends FunSuite with BeforeAndAfter with SparkSessionTestWrapper {

  test("Ryan retrieval returns empty data frame when no Ryans 
  found") {
   val sourceDF = Seq(
      ("Bryan", "21", "M")
    ).toDF("name", "age", "gender")

   val actualDF = 
     sourceDF.transform(RyanTransformer.retrieveAllRyansAge())

   assert(actualDF.count() == 0)
  }

}

请注意,我如何能够用几行代码创建一个DataFrame? 然后能够用我想测试的方法调用transform方法,并在另外两行代码中得到答案?

现在,到Java ...

class NameAndAgeExtractorTest {

@BeforeEach
void setup() {
    spark = SparkSession.builder()
             .appName("Tests")
             .master("local")
             .getOrCreate();

    javaSparkContext = new 
             JavaSparkContext(spark.sparkContext());

    namesSchema = DataTypes.createStructType(new StructField[]{
            DataTypes.createStructField("name", 
              DataTypes.StringType, false),
            DataTypes.createStructField("age", 
              DataTypes.StringType, false),
            DataTypes.createStructField("gender", 
              DataTypes.StringType, false)
    });
}
}

在这里,我们只有设定方法。 我们必须为我们的DataFrame定义一个架构,该架构将在以下测试中引用。 与Scala可以在不使用模式的情况下动态创建DataFrame的情况不同,Java要求我们保持类型不变,因此需要创建更多的代码以测试与DataFrame交互的一种小方法。

@Test
void retrieveAllRyans_returns_empty_dataset_when_no_Ryans_found() {
    List<String[]> people = new ArrayList<>();
    people.add(new String[]{"Bryan", "21", "M"});

    JavaRDD<Row> rowRDD = javaSparkContext.parallelize(people).map(RowFactory::create);

    Dataset<Row> df = spark.createDataFrame(rowRDD, namesSchema);

    Dataset<Row> resultDf = df.transform(RyanTransformer.retrieveAllRyansAge);

    Assertions.assertEquals(0L, resultDf.count());
}

在测试我们的方法之前,我们还需要完成几个步骤。 一,创建一个列表并向其中添加项目。 二,创建一个JavaRDD符合Java的需求。 最后,我们可以创建一个DataFrame,现在使用我们的方法并确认我们的结果。

现在,这仅适用于一项和一个DataFrame。 想象一下,如果我们想要多个项目,那么我们将需要的代码;如果我们需要不同类型的DataFrame,将会发生什么。 与Java中的数十行相比,您正在查看Scala的两行代码。

Conclusion

回顾我涵盖的主题:

  1. 数据框和数据集转变用户定义功能单元测试

我认为这些主题在编写Spark作业时非常重要。 如果您从这篇博客文章中删除了任何内容,希望您停下来思考一下未来Spark工作的方向。 同样,您绝对可以用Java编写它们(并编写得很好)。 但是,您将花费额外的时间来编写样板,并且从长远来看,当Scala以更少的行数向您提供时,您将不得不跳过一些麻烦的篮球。

谢谢阅读!

from: https://dev.to//stylestrip/writing-spark-scala-vs-java-49p4

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值