RDD,弹性分布式数据集

本文参考了 http://shiyanjun.cn/archives/744.html 这篇译文,原文由UC Berkeley 的实验室发表。


RDD的概述

RDD是spark计算系统的核心和精华,所以下面针对RDD进行简单的探讨。

 

先用一个小例子来具象描述一下RDD的主要工作过程:首先你从HDFS中以K-V格式读入一个文件file1,得到r1;r1是我们的第一个RDD,它被载入到内存中。然后对r1操作,找出所有包含“error”的记录,得到r2;r2是我们的第二个RDD,它是从r1经过转换得来的,也被存放在内存中。最后统计r2中的记录数并返回。我们对RDD的权限是只读的,也就是说能查看它的类信息,或者操作它得到一个新的RDD,却不能改变它。而且,假设file1有100M,在HDFS上有两个块存放在不同节点上,那么r1是有两个分区的,分别位于file1的两个块节点上。

 

现有的大部分的集群计算系统都是数据流模型的,从稳定的物理存储中加载记录,然后传入由一组确定性操作组成的DAG进行执行,最后写回到稳定存储中。这样的数据流模型的计算系统不适用于迭代式算法和交互式数据挖掘(这类计算的特点是反复操作同一组数据,或者反复执行相同的操作)。RDD就是在这样的场景下产生的。它适用于需要多次重用工作集的并行操作,所以适用于迭代式算法,交互式数据挖掘这样的应用场景;同时它还拥有数据流模型的计算系统的优势,就是容错能力,可伸缩性和位置感知调度。

 

RDD,全称弹性分布式数据集,它最大的特点就是提供了一种高度受限的共享内存模型,也就是说他是只读的记录分区的集合。首先它是只读的,一个RDD只能来自于HDFS等物理存储或者从其他RDD转换而来,这体现出了RDD的高度受限性,但同时这些限制也保证了RDD具有较好的容错机制;其次它是共享内存的,RDD的信息是存放在内存中的,相比于MapReduce要多次读写物理存储来处理中间产出,RDD大大缩减了从物理存储读入和检索所花费的时间;最后,它是分布式的,一个RDD由多个分区组成,每个分区可能存在在不同的机器节点的内存中,从而具有很好的并行性。

 

RDD的定位

我们的目标是为基于工作集的应用(即多个并行操作重用中间结果的这类应用)提供抽象,同时保持MapReduce及其相关模型的优势特性:即自动容错、位置感知性调度和可伸缩性。RDD比数据流模型更易于编程,同时基于工作集的计算也具有良好的描述能力。”spark的官方介绍。

说明RDD的定位是解决那些在并行操作中多次重用工作集的计算应用。主要包括两类。一类是迭代式算法,在机器学习和图应用中经常会用到;第二类是交互式数据挖掘工具,也就是用户反复查询一个数据子集。对于这类应用,把常用的数据缓存在内存中,能大大减小因为读取物理存储花费的时间。

同时RDD还要拥有一般的数据流模型的优点,其中最重要的就是自动容错能力。RDD通过lineage实现了较好的容错能力,下面会详细介绍。

 

RDD适合批处理操作,就是对大量记录执行相同的操作,因为执行转换和恢复数据都会很快;

RDD适合迭代计算,因为可以重用缓存在内存中的RDD,处理速度会很快;

RDD不适合异步细粒度更新,比如增量web爬虫;

RDD不适合大块读写,因为大量数据读入内存中会对内存造成较大负担。

 

RDD的描述


下面从RDD的内部结构,横向地描述RDD的基本结构;通过RDD的动作,以及RDD的依赖关系,纵向地展示RDD的变换和关系图。希望通过这三部分,能为大家勾勒出一幅RDD的基本轮廓图。

RDD的内部结构

RDD是以Java对象的结构存放在内存中。一个RDD包含:

(1)      一组RDD分区

(2)      元数据,也就是分区模式和存放位置的描述

(3)      对父RDD的一组依赖。这些依赖关系描述了RDD的lineage。

(4)      一个函数,也就是从父RDD执行何种计算得到它。

下面是spark中支持的RDD内部接口

操作

含义

partitions()

返回一组Partition对象

preferredLocations(p)

根据数据存放的位置,返回分区p在哪些节点访问更快

dependencies()

返回一组依赖

iterator(p, parentIters)

按照父分区的迭代器,逐个计算分区p的元素

partitioner()

返回RDD是否hash/range分区的元数据信息

 

RDD的动作

RDD的动作有transformation和action两种。Transformation就是把一个RDD变成另一个RDD的转换。action是把RDD通过计算后得到输出结果并返回到driver的动作。所以,一个RDD的来源只可能有两种,一种是来自于外部的物理存储,比如HDFS读入;第二种是从一个RDD转换而来。

下面是spark中支持的RDD动作

转换

map(f : T ) U) : RDD[T] ) RDD[U]
filter(f : T ) Bool) : RDD[T] ) RDD[T]
flatMap(f : T ) Seq[U]) : RDD[T] ) RDD[U]
sample(fraction : Float) : RDD[T] ) RDD[T] (Deterministic sampling)
groupByKey() : RDD[(K, V)] ) RDD[(K, Seq[V])]
reduceByKey(f : (V; V) ) V) : RDD[(K, V)] ) RDD[(K, V)]
union() : (RDD[T]; RDD[T]) ) RDD[T]
join() : (RDD[(K, V)]; RDD[(K, W)]) ) RDD[(K, (V, W))]
cogroup() : (RDD[(K, V)]; RDD[(K, W)]) ) RDD[(K, (Seq[V], Seq[W]))]
crossProduct() : (RDD[T]; RDD[U]) ) RDD[(T, U)]
mapValues(f : V ) W) : RDD[(K, V)] ) RDD[(K, W)] (Preserves partitioning)
sort(c : Comparator[K]) : RDD[(K, V)] ) RDD[(K, V)]
partitionBy(p : Partitioner[K]) : RDD[(K, V)] ) RDD[(K, V)]

动作

count() : RDD[T] ) Long
collect() : RDD[T] ) Seq[T]
reduce(f : (T; T) ) T) : RDD[T] ) T
lookup(k : K) : RDD[(K, V)] ) Seq[V] (On hash/range partitioned RDDs)
save(path : String) : Outputs RDD to a storage system, e.g., HDFS



RDD的依赖 

RDD的依赖包括宽依赖和窄依赖两种。宽依赖就是子RDD的分区依赖于父RDD的所有分区。窄依赖是子RDD的分区只依赖固定数量个父分区。比如,从一个RDD中找到所有包含“error”的记录,得到新的RDD,这个依赖是窄依赖;而MapReduce中map和reduce之间的shuffle是宽依赖。具体结构可以看下图:



RDD之间的窄依赖大有好处。首先,窄依赖的RDD之间支持pipeline计算。比如,记录通过一个RDD转换为第二个RDD后,就可以直接流水线地再转换成第三个RDD,逐条记录进行操作;而宽依赖关系下,只能等待一个RDD的数据全部计算完成,才能开始shuffle和计算子RDD。而且,窄依赖的容错机制较好,数据损坏只需要恢复父RDD的相应分区就行,而宽依赖的容错却需要对父RDD的所有分区都要恢复。

 

下面是RDD的两个主要特性的介绍,内存管理和容错机制。

内存管理

RDD的分区是以JAVA对象的方式存放在worker节点的内存中的,存放在内存中的RDD数据可供后续的多个task反复使用。用户可以自行配制是否需要对某个RDD进行缓存,也可以对RDD指定优先级。如果内存空间不足,RDD会根据LRU替换策略(最近最少使用)把一部分RDD数据存放到磁盘中,虽然这样会导致执行效率有一些下降,但确保了Job能继续正常执行。

Spark中进行Task调度时,会优先把一个任务指定给缓存了它的RDD的节点。

 

RDD的容错机制

RDD通过lineage实现容错机制。所有的RDD之间的依赖关系通过一个逻辑依赖关系图表示,这就是RDD的血统关系。当有某个RDD的分区数据损坏时,依据lineage关系找到其父RDD,重新执行从父RDD的转换得到这个RDD的数据。

注意,如果是窄依赖,那么只需要对损坏的RDD分区找到其父RDD的对应分区数据并执行转换就可以,如果父RDD的数据不存在,继续回溯找父RDD的父RDD,直到找到存在的数据为止,然后依次执行一系列转换进行数据恢复;如果是宽依赖,则需要得到父RDD的所有分区并进行shuffle,代价相对较大。

 

除了依据lineage的容错机制,RDD还支持检查点机制。因为当lineage链很长,或者是宽依赖关系的情况,采用lineage恢复数据耗时还是比较多的,这时候如果在某个合适的点处把RDD进行缓存,或者把宽依赖的父RDD进行本地物化存储,就能降低恢复的代价。检查点机制在RDD的介绍中并不是重点,不知道目前的使用情况如何,有待继续调研。

 

 

 

### RDD 弹性分布式数据集的使用场景 RDD弹性分布式数据集)作为 Spark 的核心抽象,广泛应用于各种大数据处理场景。以下是几个常见的使用场景及其示例: #### 1. **批量数据处理** RDD 适用于大规模数据的批量处理任务,例如日志分析、用户行为统计等。通过转换操作和行动操作,可以高效地完成复杂的计算。 ```python # 示例:日志文件中统计错误日志的数量 log_data = sc.textFile("hdfs://path/to/logs") # 加载日志文件到RDD error_logs = log_data.filter(lambda line: "ERROR" in line) # 过滤包含"ERROR"的日志 error_count = error_logs.count() # 统计错误日志数量 ``` 此代码片段展示了如何从 HDFS 中加载日志文件,并过滤出包含特定关键字的日志[^2]。 #### 2. **迭代算法** 由于 RDD 支持管道化操作,避免了中间结果的 I/O 开销,因此非常适合需要多次迭代的数据处理任务,如机器学习中的梯度下降算法或图计算中的 PageRank 算法。 ```python # 示例:简单的迭代计算 data = sc.parallelize([1, 2, 3, 4]) # 创建一个并行集合 for i in range(5): # 迭代5次 data = data.map(lambda x: x * 2) # 每次将元素值翻倍 result = data.collect() # 收集最终结果 ``` 此代码片段展示了一个简单的迭代计算过程,其中每次迭代都会对 RDD 进行变换操作[^1]。 #### 3. **ETL 数据处理** 在 ETL(Extract-Transform-Load)流程中,RDD 可以用于从多种数据源提取数据,进行清洗和转换后,再加载到目标存储系统中。 ```python # 示例:从 CSV 文件中读取数据并进行转换 csv_data = sc.textFile("hdfs://path/to/csv") # 从HDFS加载CSV文件 cleaned_data = csv_data.map(lambda line: line.split(",")) # 转换为列表格式 cleaned_data.saveAsTextFile("hdfs://path/to/output") # 将结果保存到HDFS ``` 此代码片段展示了如何从 HDFS 中加载 CSV 文件,对其进行清洗后,再保存回 HDFS[^3]。 #### 4. **实时流式数据处理** 虽然 Spark Streaming 是专门用于实时流式处理的模块,但其底层仍然依赖于 RDD。通过将流式数据划分为小批次,每个批次都可以作为 RDD 进行处理。 ```python # 示例:Spark Streaming 中的 RDD 处理 from pyspark.streaming import StreamingContext ssc = StreamingContext(sc, 10) # 创建StreamingContext,每10秒一个批次 lines = ssc.socketTextStream("localhost", 9999) # 从Socket接收数据 words = lines.flatMap(lambda line: line.split(" ")) # 分词 word_counts = words.map(lambda word: (word, 1)).reduceByKey(lambda a, b: a + b) # 统计词频 word_counts.pprint() # 打印结果 ssc.start() ssc.awaitTermination() ``` 此代码片段展示了如何使用 Spark Streaming 处理实时流式数据,并将其转化为 RDD 进行处理。 #### 5. **复杂数据分析** RDD 可以用于处理复杂的结构化或非结构化数据,例如 JSON 文件解析、图像处理等。 ```python # 示例:解析 JSON 文件 json_data = sc.textFile("hdfs://path/to/json") # 从HDFS加载JSON文件 parsed_data = json_data.map(lambda line: json.loads(line)) # 解析JSON字符串 result = parsed_data.filter(lambda record: record["status"] == "active").collect() # 过滤活跃记录 ``` 此代码片段展示了如何从 HDFS 中加载 JSON 文件,并对其进行解析和过滤操作。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值