《Spark指南》一、快速开始

本文主要翻译至链接且不局限于该文内容,也加入了笔者实践内容,翻译水平有限,欢迎指正,转载请注明出处。

本教程提供了使用Spark的快速介绍。 我们将首先通过Spark的交互式shell(在Python或Scala中)介绍部分API,然后演示如何使用Java,Scala和Python编写应用程序。 有关更完整的参考,请参阅编程指南

你可以先从Spark网站下载Spark的打包版本。 由于本文中我们不会使用HDFS,因此下载时不需要关注Hadoop的版本。

使用Spark Shell进行交互式分析

基础

Spark的shell环境提供了一个简单的方法来学习API,同时它也是一个强大的交互式分析数据的工具。 它可以在Scala(Scala在Java VM上运行,因此可以方便的使用现有的Java库)或Python中使用。 (本文以Scala语言为例)通过在Spark目录中运行以下命令来启动它:

./bin/spark-shell

Spark上运行的主要抽象是一个称为RDD(Resilient Distributed Dataset,弹性分布式数据集)的集合,RDDs可以从Hadoop的InputFormats(例如HDFS文件)中创建,或者从其他的RDDs转换。我们先用如下命令以Spark目录下的README文件作为数据源创建一个RDD:

scala> val textFile = sc.textFile("README.md")
textFile: org.apache.spark.rdd.RDD[String] = README.md MapPartitionsRDD[1] at textFile at <console>:25

返回的RDDs很一些方法可以执行,参考文档1:actions文档2:transformations,其中actions返回普通的值,transformations返回新的RDD。例如,下面是两个action:

scala> textFile.count() // RDD中有多少行数
res0: Long = 126 // May be different from yours as README.md will change over time, similar to other outputs

scala> textFile.first() // RDD的第一行
res1: String = # Apache Spark

下面这个例子使用filter转换,并返回一个新的RDD,它是README文件的一个子集:

scala> val linesWithSpark = textFile.filter(line => line.contains("Spark"))
linesWithSpark: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[2] at filter at <console>:27

我们也可以链式调用这些方法:

scala> textFile.filter(line => line.contains("Spark")).count() // How many lines contain "Spark"?
res3: Long = 15

更多的一些RDD操作

RDD的actions和transformations可以用来执行更复杂的运算,例如我们想找出出现最多的单词:

scala> textFile.map(line => line.split(" ").size).reduce((a, b) => if (a > b) a else b)
res4: Long = 15

第一行创建了一个新的RDD,然后将每一行映射到一个整数值。reduce函数链式处理该RDD并找到最大行计数。 其中map和reduce的参数是Scala中的语法(闭包),这里也可以使用任何Scala / Java语言的其他特性或库。 例如,下面的例子中,我们使用Math.max()函数来使这段代码更容易理解:

scala> import java.lang.Math
import java.lang.Math

scala> textFile.map(line => line.split(" ").size).reduce((a, b) => Math.max(a, b))
res5: Int = 15

一种常见的数据流模式是MapReduce,是从Hadoop流行起来的。 Spark可以轻松实现MapReduce流程:

scala> val wordCounts = textFile.flatMap(line => line.split(" ")).map(word => (word, 1)).reduceByKey((a, b) => a + b)
wordCounts: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[8] at reduceByKey at <console>:28

在这里,我们将flatMap,map和reduceByKey命令结合起来,作为(String,Int)对的RDD来计算文件中的每个字计数。 要在我们的shell中收集字数,我们可以使用collect操作:

scala> wordCounts.collect()
res6: Array[(String, Int)] = Array((means,1), (under,2), (this,3), (Because,1), (Python,2), (agree,1), (cluster.,1), ...)

缓存

Spark也允许对处理中的数据集进行缓存,数据可以缓存在集群范围内的节点内存中,以便可以对一些“热数据”快速访问。示例代码如下:

scala> linesWithSpark.cache()
res7: linesWithSpark.type = MapPartitionsRDD[2] at filter at <console>:27

scala> linesWithSpark.count()
res8: Long = 15

scala> linesWithSpark.count()
res9: Long = 15

Spark的功能绝对不仅限于处理这种只有几百行的小数据,更具有吸引力的是所有这些函数都支持在超大规模的数据集上工作,即使这些数据分布在数十或数百个节点上。你可以通过bin/spark-shell脚本连接的Spark集群中操作这些数据,详细的描述请参考编程指南

自包含应用程序

假设我们想要使用Spark API写一段自包含的应用程序,下面依次看几段示例代码:

Scala(使用sbt构建)

/* SimpleApp.scala */
import org.apache.spark.SparkContext
import org.apache.spark.SparkContext._
import org.apache.spark.SparkConf

object SimpleApp {
  def main(args: Array[String]) {
    val logFile = "YOUR_SPARK_HOME/README.md" // Should be some file on your system
    val conf = new SparkConf().setAppName("Simple Application")
    val sc = new SparkContext(conf)
    val logData = sc.textFile(logFile, 2).cache()
    val numAs = logData.filter(line => line.contains("a")).count()
    val numBs = logData.filter(line => line.contains("b")).count()
    println(s"Lines with a: $numAs, Lines with b: $numBs")
    sc.stop()
  }
}

上面这个例子创建了一个名为SimpleApp.scala的文件。注意,应用程序应该定义一个main()方法,而不是继承scala.App,继承的这种方式可能无法正常工作。

该程序实现的功能是计算Spark README文件中包含字符‘a’的行数和包含字符‘b’的行数。如果要执行这个程序,请替换正确的YOUR_SPARK_HOME路径。与前面的Spark shell初始化自己的SparkContext的例子不同,这里我们需要手动初始化一个SparkContext。程序的配置信息则通过一个SparkConf对象传递给SparkContext的构造器。

我们的程序依赖Spark API,因此我们需要准备一个sbt的配置文件,simple.sbt,它将描述Spark是程序的依赖项。这个文件也添加了一个Spark依赖的存储库:

name := "Simple Project"

version := "1.0"

scalaVersion := "2.11.7"

libraryDependencies += "org.apache.spark" %% "spark-core" % "2.1.0"

为了使sbt正常工作,我们按正常的代码目录格式分布文件SimpleApp.scala和simple.sbt,完成后,我们就可以将该应用程序打包成一个jar文件,然后使用spark-submit脚本提交到Spark执行。

# Your directory layout should look like this
$ find .
.
./simple.sbt
./src
./src/main
./src/main/scala
./src/main/scala/SimpleApp.scala

# Package a jar containing your application
$ sbt package
...
Packaging {..}/{..}/target/scala-2.11/simple-project_2.11-1.0.jar # Use spark-submit to run your application $ YOUR_SPARK_HOME/bin/spark-submit \ --class "SimpleApp" \ --master local[4] \ target/scala-2.11/simple-project_2.11-1.0.jar ... Lines with a: 46, Lines with b: 23

Java(使用Maven构建)

/* SimpleApp.java */
import org.apache.spark.api.java.*;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.function.Function;

public class SimpleApp {
  public static void main(String[] args) {
    String logFile = "YOUR_SPARK_HOME/README.md"; // Should be some file on your system
    SparkConf conf = new SparkConf().setAppName("Simple Application");
    JavaSparkContext sc = new JavaSparkContext(conf);
    JavaRDD<String> logData = sc.textFile(logFile).cache();

    long numAs = logData.filter(new Function<String, Boolean>() {
      public Boolean call(String s) { return s.contains("a"); }
    }).count();

    long numBs = logData.filter(new Function<String, Boolean>() {
      public Boolean call(String s) { return s.contains("b"); }
    }).count();

    System.out.println("Lines with a: " + numAs + ", lines with b: " + numBs);
    
    sc.stop();
  }
}

这个列子使用了Maven来编译和构建一个名为SimpleApp.java的JAR程序,它实现的功能和上文Scala一致。你也可以使用其他任意可行的构建系统。

与上文Scala一样,我们需要初始化一个SparkContext对象,上文的例子中使用了一个更友好的JavaSparkContext对象,然后创建了RDDs对象(即JavaRDD)并在他们上执行了transformations方法。最后,我们给Spark传递了继承至spark.api.java.function.Function的匿名类来执行作业。更详细的功能请参考Spark编程指南

为了构建这个程序,我们需要编写一个pom.xml文件并添加Spark作为依赖项,注意,Spark的artifacts使用了Scala的版本标记(2.11表示的是scala的版本)。

<project>
  <groupId>edu.berkeley</groupId>
  <artifactId>simple-project</artifactId>
  <modelVersion>4.0.0</modelVersion>
  <name>Simple Project</name>
  <packaging>jar</packaging>
  <version>1.0</version>
  <dependencies>
    <dependency> <!-- Spark dependency -->
      <groupId>org.apache.spark</groupId>
      <artifactId>spark-core_2.11</artifactId>
      <version>2.1.0</version>
    </dependency>
  </dependencies>
</project>

按目录组织这些文件,如:

$ find .
./pom.xml
./src
./src/main
./src/main/java
./src/main/java/SimpleApp.java

然后,使用maven命令进行编译和构建,之后就可以使用spark-submit脚本提交到Spark上执行:

# Package a JAR containing your application
$ mvn package
...
[INFO] Building jar: {..}/{..}/target/simple-project-1.0.jar

# Use spark-submit to run your application
$ YOUR_SPARK_HOME/bin/spark-submit \
  --class "SimpleApp" \
  --master local[4] \
  target/simple-project-1.0.jar
...
Lines with a: 46, Lines with b: 23

Python

"""SimpleApp.py"""
from pyspark import SparkContext

logFile = "YOUR_SPARK_HOME/README.md"  # Should be some file on your system
sc = SparkContext("local", "Simple App")
logData = sc.textFile(logFile).cache()

numAs = logData.filter(lambda s: 'a' in s).count()
numBs = logData.filter(lambda s: 'b' in s).count()

print("Lines with a: %i, lines with b: %i" % (numAs, numBs))

sc.stop()

类似的,python版本创建了一个SimpleApp.py(使用了pyspark,Spark Python API),功能与前述一致。python版本同样需要创建一个SparkContext,然后用它来创建RDDs,之后向它传递lambda表示的函数。如果应用程序使用了第三方的库,则需要我们将它们达成zip包,并在执行spark-submit时添加–py-files选项。在这个例子中,由于没有依赖第三方库,因此我们可以直接提交应用程序:

# Use spark-submit to run your application
$ YOUR_SPARK_HOME/bin/spark-submit \
  --master local[4] \
  SimpleApp.py
...
Lines with a: 46, Lines with b: 23

进一步学习

如果你按照前文进行了实践,那么恭喜你已经成功运行了你的第一个Spark应用程序。接下来,你可以:

  • 学习Spark programming guide以进一步了解如果编写更丰富的功能
  • 想要了解如果在集群中提交应用程序,可以参考deployment overview
  • 最后,Spark的安装包也包含了一些实例,位于example目录(Scala, Java, Python, R),你可以像下面这样执行它们:
# For Scala and Java, use run-example:
./bin/run-example SparkPi

# For Python examples, use spark-submit directly:
./bin/spark-submit examples/src/main/python/pi.py

# For R examples, use spark-submit directly:
./bin/spark-submit examples/src/main/r/dataframe.R

相关的文章

    原文作者:Flowaway
    原文地址: https://www.jianshu.com/p/38aae18c1886
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞