美文网首页
2 RDD初识之旅

2 RDD初识之旅

作者: 云端上的男人 | 来源:发表于2017-05-26 17:00 被阅读87次

    By云端上的男人—DT大数据梦工厂

    RDD,全称为Resilient Distributed Datasets,是一个容错的、并行的数据结构,可以让用户显式地将数据存储到磁盘和内存中,并能控制数据的分区。

    笔者所写的博客面对的人员是有scala基础以及对进程和线程有相关概念的人员和spark会基本使用的人员,如果大家有的对scala不是很熟悉的话,请大家观看家林老师的相关的scala教程链接:http://pan.baidu.com/s/1sljSvut密码:eea6.Spark基础相关的链接链接:http://pan.baidu.com/s/1eSvKaFS密码:y95q

    不过笔者还是喜欢把RDD看成容器和集合,从这个角度可以引出笔者所提的类比思想。在上一篇文章中,笔者提到我们平时在单进程中使用的Scala集合也是有一些对于该集合操作的算子(比如map,filter,groupby等),那么RDD这样的集合也是有类似的算子的存在。不过RDD本身有一些特性的存在:RDD中有数据的分区,分别在不同的节点之上。

    RDD和Scala集合也有一些相同点,比如RDD处理map,filter等算子的时候,每个数据分区都会用这个算子去处理数据。而且每个分区的处理逻辑和本地的Scala集合处理逻辑是相同的。但是某些处理逻辑就会有所不同了,因为要跨集群去抓取数据。

    关于RDD的特性,关于RDD的完整定义,大家可以去网上自行的搜索。接下来,笔者会用自己的方式来给大家详细的阐述一下自己对RDD的认识。

    RDD被设计为集合的原因就是在于我们的数据量很大,以至于单个节点无法容纳这么多的数据。所以RDD的数据会分布在不同的节点之上。由于数据在不同的节点之上,那么RDD中的操作的算子就会出现一些在网络环境中要考虑的问题。

    从图1中可知,RDD之间有依赖关系,把被依赖的RDD称之为父RDD,依赖的RDD称之为子RDD。且有以下特性。

    RDD的窄依赖就是每个父RDD的数据的分片只能让子RDD中的一个数据分片去继承。(这种情况也可能会导致数据的拉取操作,但不会很频繁)

    RDD的宽依赖就是每个父RDD的数据的分片让子RDD中的数据所有个分片去继承。(这种情况是数据操作的算子会导致子RDD中的分区的数据可能要到父分区中其他的节点去拉取数据,会很频繁)

    而且从图中,我想读者会考虑一个问题,为什么在RDD中有RDD之间依赖的关系的存在?这个就要说到RDD中的设计思想。在大数据量的操作中,数据加载到内存中是一件昂贵的事情,而且在平时的业务逻辑处理中,对RDD的操作也绝不仅仅是一两个算子就能搞定的。这就需要极大化的使用内存中的数据结构,以及尽可能的减少由于宽依赖导致的数据在网络传输的性能下降与不稳定等问题。所以在做数据算子操作的时候,RDD底层的数据并没有被运行加载,RDD只是代表真是数据的元数据信息。父RDD对算子的操作转换生成了另一个RDD。这就是Spark称之为的transform(转换)算子。而在众多RDD实例中,RDD被分为两大部分:宽依赖RDD和窄依赖RDD。在窄依赖中,子RDD不需要生成自己的数据分区,只需要在父RDD分区中处理算子即可。而在宽依赖中,子RDD就必须要有自己的数据的分区,因为每个父RDD的数据分区要向子RDD中的多个分区写入数据。

    既然如此,我们就必须要考虑为什么数据会有宽依赖,也就说为什么数据必须要频繁地经过网络的传输。这就要考虑我们对数据操作算子的模型。考虑map算子:把集合中的数据从一种状态转换为另一中状态,每个子RDD数据分区之中数据完全可以在依赖的父RDD的数据分区中做集合状态的操作,这是最理想的情况。但是还有一类算子可就没有那么的幸运了,比如reduceByKey算子(对父RDD中数据进行相同的key进行聚合操作):顾名思义,父RDD进行reduceByKey操作,那么父RDD的每个分区中可能有相同的key存在,也有不同的key的存在,那么有个问题就要考虑了,每个父RDD中的一个数据分区中究竟有多少不同的key存在,这个问题我们不知道,但是直接影响对子RDD生成多少个数据分区,因为子RDD数据分区分别要容纳父RDD各个分区数据相同key值的(key,value)的部分,这种情况下也导致严重的网络数据抓取的情况。所以RDD提供了分区器,默认父RDD给子RDD进行分区。父RDD把分区好的数据放在每个数据分区所在的进程中,然后在Driver的参与下子RDD去抓取属于自己的数据。如果读者心思缜密的话,就会想到,宽依赖子RDD数据分区在父RDD用分区器做分区之后,也许分区比父RDD多,也可能少,也可能相等。但是子RDD分区的位置我们该如何考虑?这种情况,Driver首先查询拥有父RDD数据最多的位置成为子RDD的数据分区,如果没有就随机在某个CoarseGrainedExecutorBackend进程中把子RDD数据分区放入其中。

    注意:之前RDD之间的状态转换并没有使RDD中数据元素运行起来,也就是说之前的转换操作是属于RDD本身作为元数据的状态操作,通常把RDD元数据的操作称之为lazy操作。真正触发RDD数据处理的操作称之为action(动作)。只要在任何一个RDD使用action操作,即可触发从最开始的RDD到最后的RDD的一连串的transform算子操作。例如action操作的方法有collect等。如下图所示,简化的画法


    以上只是从理论的角度来囊括RDD总体的架构,其中这里面有很多的细节笔者并没有展开去展示,因为是涉及到一些其他概念的展示。接下来的章节我们再去详细讨论。

    图2中有多了一个Stage概念,Stage的划分是通过宽依赖来进行划分,关于为什么会有这种宽依赖的划分?这个主要就是考虑到最大化的数据的管道的操作,就是所有的窄依赖都会在一个Stage中来处理,也就是说,在这个Stage中从第一个RDD到这个Stage中最后一个RDD,笔者之前所说的在窄依赖中子RDD中的数据分区是不存在的,而是直接在父RDD中的依赖的数据分区做数据操作,那么由此可以推导出

    ,父RDD的分区又依赖于祖父RDD的分区,那么父RDD的分区的数据操作就要转到祖父RDD的数据分区操作,以此类推,所有的操作都会转到这个Stage中第一个RDD中的数据分区的操作。那么在从效率上来考虑的话,Stage中RDD的数据只需要在数据加一次即可。如下图所示这是简化的Stage中的数据转换操作。

    接下来,笔者把图2再次简化一下,让读者更加简化看待Stage之间的依赖关系。

    那么接下来,笔者需要把Spark的另一个概念引入到这里,那就是Job的概念。关于Job的详细概念,读者可以从网上做更加详细查找。

    Job的作用可以对这些RDD、Stage这些个细节又做了一次更高层封装,也就说用户在写DriverProgram中,每一次的一个action操作,都会触发一次Job的执行。而用户在书写自己代码时候,可以批处理多个Job来运行这个作业。那么到这个地步将会出现另一个问题,如果用户运行多个Job,那么势必要考虑这些Job之间运行顺序。可能有的Job运行时间长,有的运行时间短。用户程序Job在Driver中运行默认是FIFO(先入先出)模式。当然还有FAIR(公平调度)模式。我们目前只考虑FIFO模式。下图是笔者另一个简化DriverProgram中的Job调度图。

    相关文章

      网友评论

          本文标题:2 RDD初识之旅

          本文链接:https://www.haomeiwen.com/subject/gnexfxtx.html