Spark学习笔记(三)-RDD(弹性分布式数据集)

RDD是一些对象的只读集合, 被划分到多台机器上, 并且在某个划分块丢失之后可以重建. 用户可以显式的把RDD缓存在内存中, 方便在类似于Map-Reduce的并发操作中重用, 这也是为什么Spark比较适合处理迭代式Job的原因. RDD通过"血统"(lineage)的概念来保证容错性, 当RDD的一个划分块丢失之后, 该RDD知道怎样从其他的RDD中重建该划分块. RDD中的元素不需要被存储在物理设备上, 每个RDD都有一个句柄, 其中包含了如何从其他存储在物理设备上的数据计算该RDD的信息.

1. RDD的类型


RDD有两种类型: 并行集合和Hadoop数据集. 可以在两种类型上执行相同的方法.

1.1 并行集合(Parallelized Collections).


在一个Scala集合(一个 Seq 对象)上调用 SparkContext 的 parallelize() 方法就可以得到并行集合. 该集合中的元素会被拷贝, 构成一个分布式的数据集, 然后可以对其进行并发的操作. 下例为基于一个 array 构造并发集合:

在构造完成之后, 这个分布式的数据集就可以进行并行的操作. 比如调用 distData.reduce(_ + _) 来对数组中的元素求和.

并行集合的一个重要的参数是数据集被切片的个数. Spark会对每个切片运行一个task. 通常来说对于每个CPU需要有2-4个切片. 一般情况下, Spark会根据集群的状况自动设置数据集的切片数, 但是可以通过 sc.parallelize(data,10) 来设置.

1.2 Hadoop数据集


Spark可以基于在HDFS(或者AmazonS3, Hypertable, HBase)等分布式文件系统创建分布式数据集. Spark支持文本文件, Sequence File等Hadoop输入格式.

对于文本文件, 可以使用 SparkContext的 textFile() 方法. 该方法的参数为文本文件的URI:

一旦创建完毕, 可以在 distFile 上执行并行操作:  distFile.map(_.size).reduce(_ + _)

默认情况下, Spark会为文本文件的每一个块创建一个切片, 也可以像之前的并发数据集一样设置切片数, 但是切片的个数不能少于文件块的个数.

对于Sequence File, 可以使用 SparkContext 的 sequenceFile[K,V] 方法, 其中K和V是文件中的键与值的类型.

2. RDD的操作


RDD支持两种操作: transformation和action.

transformation会基于一个RDD生成一个新的RDD. Spark中的transformation是惰性的(lazy), 亦即transformation不会立刻计算出其结果, 只有当一个action需要在transformation生成的RDD上得到结果并返回给驱动程序时才会计算. 这种设计使得Spark可以更加高效的运行: 对于一个由map生成, 并且会执行一个reduce操作的数据集, 只有reduce的结果需要返回给驱动程序, 而不是该数据集.

默认情况下, 每当你需要在一个被transformation生成的数据集上执行操作时, 该数据集都需要被重新计算. 但是, 我们可以把该数据集用 persist 或者 cache 方法persist在内存中, 这样这个在下次访问该数据集时速度会大大加快.

action会返回驱动程序(driver program)一个值. 驱动程序实现了应用程序高层控制流, 并且会并行的执行操作.

常用的transformation操作有
  • map(func) 对输入并行数据集中的每个元素都执行func, 生成新的并行数据集
  • filter(func) 对输入并行数据集中的每个元素, 如果func返回值为 trus , 则在输出并行数据集中保留该元素, 否则过滤.
  • flatMap(func) 类似于map, 只是每个输入元素可能被映射到0个或者多个输出元素(所以func用该返回一个 Seq 而不是单个元素)
  • mapPartitions(func) 类似于map, 但是是在RDD的每个划分块上单独执行. 所以func的类型为 Iterator[T] =>Iterator[U]
  • mapPartitionsWithSplit(func) 类似于mapPattitions, 但是会在func的参数中加一个整数来表示split块的索引 (Int,Iterator[T]) => Iterator[U]
  • sample(withReplacement, fraction, seed) 从数据中抽样fraction比例的样本.
  • union(otherDataset) 合并输入数据集和参数中指定的数据集
  • distinct([numTasks]) 返回一个数据集, 只包含输入数据集中互不相同的元素
  • groupByKey([numTasks]) 当输入数据集包含的是(K,V)对时, 返回(K, Seq[V]), 默认numTasks为8
  • reduceByKey(func, [numTasks]) 当输入数据集包含的是(K,V)对时, 返回(K, V), 相当于是先对(K, V)执行groupByKey, 再对每个K的Seq[V]执行func.
  • sortByKey([ascending], [numTasks]) 按照K排序, 其中K需要实现Ordered
  • join(otherDataset, [numTasks]) 将输入数据集(K, V)和另外一个数据集(K, W)进行Join, 得到(K, (V, W))
  • cogroup(otherDataset, [numTasks])  将输入数据集(K, V)和另外一个数据集(K, W)进行cogroup, 得到一个格式为(K, Seq[V], Seq[W])的数据集
  • cartesian(otherDataset) 做笛卡尔积: 对于数据集T合U, 得到(T, U)格式的数据集

常用的action操作有
  • reduce(func) 对数据集的所有元素执行聚集(func)函数, 该函数必须是可交换的
  • collect() 将数据集中的所有元素以一个array的形式返回
  • count() 返回数据集中元素的个数
  • first() 返回数据集中的第一个元素, 类似于take(1)
  • take(n) 返回一个包含数据集中前n个元素的数组, 当前该操作不能并行.
  • takeSample(withReplacement, num, seed) 返回包含随机的num个元素的数组.
  • saveAsTextFile(path) 把数据集中的元素写到一个文本文件. Spark会对每个元素调用toString方法来把每个元素存成文本文件的一行.
  • saveAsSequenceFile(path) 把数据集中的元素存成Hadoop SequenceFile的格式. 只有当RDD的格式为键值对时才可以.
  • countByKey() 只有当RDD的格式为键值对时才可以. 返回一个(K, Int)的map, Int为K的个数.
  • foreach(func) 对数据集中的每个元素都执行func.

3. RDD的持久化


Spark中, 用户可以显式的把RDD缓存(persist, 或cache)在内存中. 当缓存一个RDD时, 每个节点都会把计算出的那个切片存储在内存中, 并且会在之后的action中重用这个切片. 这样之后的action就会更快.

可以调用RDD的 persist() 或者 cache() 来进行缓存. 当该RDD第一次被计算出来时, 就会存储在内存中. 这种缓存是由容错性的, 如果RDD的某个块丢失了, 则会根据产生这个RDD的transformation来生成这个块.

每个RDD可以使用不同的存储级别, 可以把这个数据集存储在硬盘, 或者以序列化的Java对象的形式存储在内存中, 或者在多个节点上进行备份. 可以通过向 persist() 方法传递 org.apche.spark.storage.StorageLevel 来选择存储级别.  cache() 方法相当于是选择了 StorageLevel.MEMORY_ONLY (以序列化的对象的身份存储在内存中). 以下是完整的存储级别
  • MEMORY_ONLY 以反序列化Java对象的心事存储在内存, 如果RDD太大不能完全放在内存, 则多余的一些划分块不会被存储, 会在需要时现生成
  • MEMORY_AND_DISK 以反序列化Java对象的心事存储在内存, 如果RDD太大不能完全放在内存, 则把多余的划分块存储在硬盘
  • MEMORU_ONLY_SER 以序列化的Java对象的形式存储在内存. 这样比比反序列化Java对象回省空间, 但是费CPU
  • MEMORY_AND_DISK_SER 顾名思义, 以序列化Java对象的行书存储在内存, 多余的块存储在硬盘上
  • DISK_ONLY 把RDD存储在硬盘
  • MEMORY_ONLY_2, MEMORY_AND_DISK_2 与以上的存储级别类似, 但是会备份2份

关于选择存储级别的一些思路

Spark存储级别是为了在内存使用和CPU效率之间做权衡.
  • 最好使用默认的存储级别(MEMORY_ONLY), 这是CPU效率最好的选项, 使RDD上的操作尽可能快
  • 如果RDD比较大不能存储在内存, 则尝试MEMORY_ONLY_SER, 并且选择一个块的序列化库, 使得既能节省空间, 又不太影响速度.
  • 除非计算数据集的代价很高或者生成该数据集会过滤掉很多数据, 否则不要把数据存储在硬盘, 因为重新计算一个划分块比从硬盘中读取要快很多.
  • 当需要快速容错时, 使用带备份的存储级别. 虽然所有的存储级别都可以通过重新计算丢失的数据来达到容错, 但是备份可以使得无需重新计算就能继续在RDD上执行task.
  • 转载:http://www.kemaswill.com/system/spark/spark%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E4%B8%89-rdd%E5%BC%B9%E6%80%A7%E5%88%86%E5%B8%83%E5%BC%8F%E6%95%B0%E6%8D%AE%E9%9B%86/
阅读更多

没有更多推荐了,返回首页