说明
- 本篇仅讨论运行在集群模式下的Spark。
- 本篇阅读时间在15min。
- 本篇用例均在spark-shell交互式脚本。
Spark有两个重要的概念,一个是RDD,另一个是Shard Variable。下面详细介绍。
1 RDD
RDD全称是Resilient Distributed Dataset,弹性分布式数据集。Spark运行都是建立在这一抽象的集合上,使得它可以以基本一致的方式应对不同的大数据处理场景,包括MapReduce、Streaming、SQL、Machine Learning、Graph等。
Spark支持多种编程语言,包括Python、Scala及R,RDD同样支持存储这些语言的对象。
1.1 创建RDD
Spark支持从多个地方中创建RDD,包括本地文件系统,HDFS文件系统,HBase及内存。
1.1.1 本地文件系统
//从本地文件/user/root/data.txt创建为不可变RDD
val distFile = sc.textFile("file:///user/root/data.txt")
1.1.2 HDFS文件系统
Spark默认从HDFS文件系统获取数据
val distFile = sc.textFile("/user/root/data.txt")
//指定HDFS url
val distFile = sc.textFile("hdfs://localhost:9000/user/data.txt")
1.1.3 内存
通过调用SparkContext的parallelize方法创建。
val data = Array(1, 2, 3, 4, 5)
val distData = sc.parallelize(data)
创建完RDD后我们就可以在集群对这些数据集进行并行化处理。
1.2 RDD操作
RDD支持两种类型操作:
- 转化操作(transformation),从一个已有的RDD创建一个新的RDD;
- 行动操作(action),对RDD进行计算出,并把结果返回到程序中,或把结果存储到外部存储系统(如HDFS)中。
转化和行动操作重要的区别是转化操作是惰性执行,只有等到行动操作开始执行时转化操作才将执行。
1.2.1 转化操作
以下RDD方法均为转化操作:
- map
- filter
- flatMap
- mapPartitions
- sample
- union
- intersection
- distinct
- groupByKey
- reduceByKey
- aggregateByKey
- sortByKey
- join
- cogroup
- pipe
- repartition
转化操作方法返回的是新的RDD。
1.2.2 行动操作
以下RDD方法均为行动操作:
- reduce
- reduceByKey
- collect
- count
- first
- take
- takeSample
- takeOrdered
- saveAsTextFile
- saveAsSequenceFile
- saveAsObjectFile
- countByKey
- foreach
行动操作方法返回的是最终结果。
1.3 RDD缓存
Spark对RDD操作是惰性求值的,有时又希望能够多次使用同一个RDD,如果简单地对RDD调用行动操作,每次都会重新计算RDD,这在迭代算法中消耗格外大,因为迭代算法常常会多次使用同一组数据。
为了避免多次计算同一个RDD,可以让Spark对数据进行持久化。使用RDD对象的persist()方法对数据进行持久化。该方法支持传入参数,用于指定持久化位置及持久化方式。
val lines = sc.textFile("file:///tmp/README.md")
import org.apache.spark.storage._
lines.persist(StorageLevel.MEMORY_ONLY)
1.3.1 移除数据
Spark会自动监控缓存的使用,并基于LRU(最近最少使用)算法自动移除数据。如果你想手动移除数据,你可以使用unpersist方法。默认情况下该方法不会考虑数据是否在使用,如果你想等资源不再使用时再移除你可以指定blocking=true。
2 Shared Variable
当远程节点执行一个传入Spark操作的函数时,该函数所有的变量都会被拷贝一份到该节点,当这些变量在其他节点有更新时当前节点变量不会更新,直至返回给驱动器程序。通常意义上来说,横跨Task的读写变量是不高效的。Spark提供两种共享变量:
- broadcast variables,广播变量
- accumulators,累加变量
2.1 broadcast variables(广播变量)
广播变量允许开发人员在每个节点上缓存一个只读变量,无需每个任务中拷贝。广播变量可用于高效的为每个节点拷贝一份大的输入数据集。Spark同样将尝试使用高效的算法来广播变量,用以减少传输成本。
显示创建广播变量仅在Task需要横跨多个Stage且需要同一份数据或者缓存数据为逆序列化格式时才有用。
创建广播变量的方法是:
scala> val broadcastVar = sc.broadcast(Array(1, 2, 3))
broadcastVar: org.apache.spark.broadcast.Broadcast[Array[Int]] = Broadcast(0)
scala> broadcastVar.value
res0: Array[Int] = Array(1, 2, 3)
从value字段获取广播变量值。正如名字所言,广播变量是单向从驱动器到task发送的,广播变量无法更新,无法对驱动器的更新操作。保证所有节点均获取同一份数据。
调用unpersist()方法可以释放广播变量所占用的资源。如果这个资源再次被使用,该变量将重新被广播。如果要永久移除广播变量占用的资源,可以调用destory()方法。
2.2 Accumulators(累加变量)
类似于C语言中的静态变量,Spark支持数值型及用户自定义型累加变量。其特点是允许多个Task按顺序更新同一个累加变量。创建变量时可以通过SparkContext.longAccumulator()或者SparkContext.doubleAccumulator()创建long类型、double型累加器。Task可以使用add方法向累加变量添加内容。执行器程序无法读取累加变量,仅有驱动器程序才可以通过value方法读取。
scala> val accum = sc.longAccumulator("My Accumulator")
accum: org.apache.spark.util.LongAccumulator = LongAccumulator(id: 0, name: Some(My Accumulator), value: 0)
scala> sc.parallelize(Array(1, 2, 3, 4)).foreach(x => accum.add(x))
...
10/09/29 18:41:08 INFO SparkContext: Tasks finished in 0.317106 s
scala> accum.value
res2: Long = 10
当然你也可以自定义累加变量。