Learning Spark: lightning-fast big data analysis (1)

来源:互联网 发布:新开淘宝店物流 编辑:程序博客网 时间:2024/05/16 11:40

1. Spark中的RDD 就是一个不可变的分布式对象集合。每个RDD 都被分为多个分区,这些分区运行在集群中的不同节点上。RDD是Spark中的抽象数据结构类型,任何数据在Spark中都被表示为RDD。从编程的角度来看,RDD可以简单看成是一个数组。和普通数组的区别是,RDD中的数据是分区存储的,这样不同分区的数据就可以分布在不同的机器上,同时可以被并行处理。

2.转化操作和行动操作的区别在于Spark 计算RDD 的转化操作和行动操作的区别在于Spark 计算RDD 的方式不同。RDD 的转化操作是返回一个“新的RDD”的操作,比如map() 和filter(),而行动操作则是向驱动器程序返回结果或把结果写入外部系统的操作,会触发实际的计算,比如count() 和first()。如果对于一个特定的函数是属于转化操作还是行动操作感到困惑,你可以看看它的返回值类型:转化操作返回的是RDD,而行动操作返回的是其他的数据类型。

3.RDD 还有一个collect() 函数,可以用来获取整个RDD中的数据。只有当你的整个数据集能在单台机器的内存中放得下时,才能使用collect(),因此,collect() 不能用在大规模数据集上。

4.Spark 使用惰性求值,这样就可以把一些操作合并到一起来减少计算数据的步骤。

5.转化操作map()---接收一个函数,把这个函数用于RDD 中的每个元素,将函数的返回结果作为结果RDD中对应元素的值。
 转化操作filter()---接收一个函数,并将RDD 中满足该函数的元素放入新的RDD 中返回。
 转化操作flatMap()---分别应用到了输入RDD的每个元素上,每个输入元素生成多个输出元素,得到的是一个包含各个迭代器可访问的所有元素的RDD。


6.有些函数只能用于特定类型的RDD,在Scala 中,将RDD 转为有特定函数的RDD是由隐式转换来自动处理的。隐式转换虽然强大,但是会让阅读代码的人感到困惑。


7.Spark RDD 是惰性求值,而有时希望能多次使用同一个RDD,如果简单地对RDD 调用行动操作,Spark 每次都会重算RDD 以及它的所有依赖。这在迭代算法中消耗格外大,因为迭代算法常常会多次使用同一组数据。让Spark 持久化存储一个RDD 时,计算出RDD 的节点会分别保存它们所求出的分区数据。如果要缓存的数据太多,内存中放不下,Spark 会自动利用最近最少使用(LRU)的缓存策略把最老的分区从内存中移除。RDD 还有一个方法叫作unpersist(),调用该方法可以手动把持久化的RDD 从缓存中移除。


8.在分布式程序中,通信的代价是很大的,因此控制数据分布以获得最少的网络传输可以极大地提升整体性能。和单节点的程序需要为记录集合选择合适的数据结构一样,Spark 程序可以通过控制RDD 分区方式来减少通信开销。


9.如果没有将partitionBy() 转化操作的结果持久化persist(),那么后面每次用到这个RDD 时都会重复地对数据进行分区操作。不进行persist()持久化会导致整个RDD 谱系图重新求值。那样的话,partitionBy() 带来的好处就会被抵消,导致重复对数据进行分区以及跨节点的混洗,和没有指定分区方式时发生的情况十分相似。


10.SequenceFile 是由没有相对关系结构的键值对文件组成的常用Hadoop 格式,如果你在使用一个已有的Hadoop 系统,数据很有可能是以SequenceFile 的格式供你使用的。由于Hadoop 使用了一套自定义的序列化框架,因此SequenceFile 是由Hadoop 的Writable接口的元素组成。Hadoop 的RecordReader 会为每条记录重用同一个对象,因此直接调用RDD的cache 会导致失败;实际上,你只需要使用一个简单的map() 操作然后将结果缓存即可。还有,许多Hadoop Writable 类没有实现java.io.Serializable接口,因此为了让它们能在RDD 中使用,还是要用map() 来转换它们。


11.对象文件看起来就像是对SequenceFile 的简单封装,它允许存储只包含值的RDD。和SequenceFile 不一样的是,对象文件是使用Java 序列化写出的。与其他文件格式不同的是,对象文件通常用于Spark 作业间的通信。要保存对象文件,只需在RDD 上调用saveAsObjectFile 就行了。读回对象文件也相当简单:用SparkContext 中的objectFile() 函数接收一个路径,返回对应的RDD。使用对象文件的主要原因是它们可以用来保存几乎任意对象而不需要额外的工作。


12.通常在向Spark 传递函数时,比如使用map() 函数或者用filter() 传条件时,可以使用驱动器程序中定义的变量,但是集群中运行的每个任务都会得到这些变量的一份新的副本,更新这些副本的值也不会影响驱动器中的对应变量。Spark 的两个共享变量,累加器与广播变量,分别为结果聚合与广播这两种常见的通信模式突破了这一限制。


13.第一种共享变量,即累加器,提供了将工作节点中的值聚合到驱动器程序中的简单语法。累加器的一个常见用途是在调试时对作业执行过程中的事件进行计数。(eg, val blankLines = sc.accumulator(0) // 创建Accumulator[Int]并初始化为0 )。工作节点上的任务不能访问累加器的值。从这些任务的角度来看,累加器是一个只写变量。累加器的值只有在驱动器程序中可以访问,所以检查也应当在驱动器程序中完成。


14.第二种共享变量类型,是广播变量。它可以让程序高效地向所有工作节点发送一个较大的只读值,以供一个或多个Spark 操作使用(eg,val signPrefixes = sc.broadcast(loadCallSignTable()) )。广播变量其实就是类型为spark.broadcast.Broadcast[T] 的一个对象,其中存放着类型为T 的值。可以在任务中通过对Broadcast 对象调用value 来获取该对象的值。这个值只会被发送到各节点一次,使用的是一种高效的类似BitTorrent 的通信机制。变量只会被发到各个节点一次,应作为只读值处理(修改这个值不会影响到别的节点)。满足只读要求的最容易的使用方式是广播基本类型的值或者引用不可变对象。在这样的情况下,你没有办法修改广播变量的值,除了在驱动器程序的代码中可以修改。


15.如果Scala、Java 以及Python 都不能实现你需要的功能,那么Spark 也为这种情况提供了一种通用机制,可以将数据通过管道传给用其他语言编写的程序,比如R 语言脚本。Spark 在RDD 上提供pipe() 方法。Spark 的pipe() 方法可以让我们使用任意一种语言实现Spark 作业中的部分逻辑,只要它能读写Unix 标准流就行。通过pipe(),你可以
将RDD 中的各元素从标准输入流中以字符串形式读出,并对这些元素执行任何你需要的操作,然后把结果以字符串的形式写入标准输出——这个过程是RDD 的转化操作过
程。eg,

val distances = contactsContactLists.values.flatMap(x=>x.map(y=>s"$y.contactlay,$y.contactlong,$y.mylat,$y.mylong")).pipe(Seq(SparkFiles.get(distScriptName)));
println(distances.collect().toList);






















0 0